summaryrefslogtreecommitdiffstats
path: root/src/tools
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools')
-rw-r--r--src/tools/files.c736
-rw-r--r--src/tools/sss_groupadd.c155
-rw-r--r--src/tools/sss_groupdel.c155
-rw-r--r--src/tools/sss_groupmod.c246
-rw-r--r--src/tools/sss_groupshow.c944
-rw-r--r--src/tools/sss_sync_ops.c1838
-rw-r--r--src/tools/sss_sync_ops.h125
-rw-r--r--src/tools/sss_useradd.c349
-rw-r--r--src/tools/sss_userdel.c205
-rw-r--r--src/tools/sss_usermod.c265
-rw-r--r--src/tools/tools_util.c520
-rw-r--r--src/tools/tools_util.h108
12 files changed, 5646 insertions, 0 deletions
diff --git a/src/tools/files.c b/src/tools/files.c
new file mode 100644
index 000000000..6c6447059
--- /dev/null
+++ b/src/tools/files.c
@@ -0,0 +1,736 @@
+/*
+ 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"
+#include "tools/tools_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/src/tools/sss_groupadd.c b/src/tools/sss_groupadd.c
new file mode 100644
index 000000000..15eed100e
--- /dev/null
+++ b/src/tools/sss_groupadd.c
@@ -0,0 +1,155 @@
+/*
+ SSSD
+
+ sss_groupadd
+
+ Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009
+
+ 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/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <talloc.h>
+#include <popt.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "tools/tools_util.h"
+#include "tools/sss_sync_ops.h"
+
+int main(int argc, const char **argv)
+{
+ gid_t pc_gid = 0;
+ int pc_debug = 0;
+ 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 },
+ { "gid", 'g', POPT_ARG_INT, &pc_gid,
+ 0, _("The GID of the group"), NULL },
+ POPT_TABLEEND
+ };
+ poptContext pc = NULL;
+ struct tools_ctx *tctx = NULL;
+ int ret = EXIT_SUCCESS;
+ const char *pc_groupname = NULL;
+
+ debug_prg_name = argv[0];
+
+ ret = set_locale();
+ if (ret != EOK) {
+ DEBUG(1, ("set_locale failed (%d): %s\n", ret, strerror(ret)));
+ ERROR("Error setting the locale\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* parse params */
+ pc = poptGetContext(NULL, argc, argv, long_options, 0);
+ poptSetOtherOptionHelp(pc, "GROUPNAME");
+ if ((ret = poptGetNextOpt(pc)) < -1) {
+ usage(pc, poptStrerror(ret));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ debug_level = pc_debug;
+
+ /* groupname is an argument, not option */
+ pc_groupname = poptGetArg(pc);
+ if (pc_groupname == NULL) {
+ usage(pc, _("Specify group to add\n"));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ CHECK_ROOT(ret, debug_prg_name);
+
+ ret = init_sss_tools(&tctx);
+ if (ret != EOK) {
+ DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
+ if (ret == ENOENT) {
+ ERROR("Error initializing the tools - no local domain\n");
+ } else {
+ ERROR("Error initializing the tools\n");
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* if the domain was not given as part of FQDN, default to local domain */
+ ret = parse_name_domain(tctx, pc_groupname);
+ if (ret != EOK) {
+ ERROR("Invalid domain specified in FQDN\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ tctx->octx->gid = pc_gid;
+
+ /* arguments processed, go on to actual work */
+ if (id_in_range(tctx->octx->gid, tctx->octx->domain) != EOK) {
+ ERROR("The selected GID is outside the allowed range\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ start_transaction(tctx);
+ if (tctx->error != EOK) {
+ goto done;
+ }
+
+ /* groupadd */
+ ret = groupadd(tctx, tctx->ev, tctx->sysdb, tctx->handle, tctx->octx);
+ if (ret != EOK) {
+ tctx->error = ret;
+
+ /* cancel transaction */
+ talloc_zfree(tctx->handle);
+ goto done;
+ }
+
+ end_transaction(tctx);
+
+done:
+ if (tctx->error) {
+ ret = tctx->error;
+ switch (ret) {
+ case ERANGE:
+ ERROR("Could not allocate ID for the group - domain full?\n");
+ break;
+
+ case EEXIST:
+ ERROR("A group with the same name or GID already exists\n");
+ break;
+
+ default:
+ DEBUG(1, ("sysdb operation failed (%d)[%s]\n", ret, strerror(ret)));
+ ERROR("Transaction error. Could not add group.\n");
+ break;
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = EXIT_SUCCESS;
+fini:
+ talloc_free(tctx);
+ poptFreeContext(pc);
+ exit(ret);
+}
+
diff --git a/src/tools/sss_groupdel.c b/src/tools/sss_groupdel.c
new file mode 100644
index 000000000..e5b043e27
--- /dev/null
+++ b/src/tools/sss_groupdel.c
@@ -0,0 +1,155 @@
+/*
+ SSSD
+
+ sss_groupdel
+
+ Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009
+
+ 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/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <talloc.h>
+#include <popt.h>
+
+#include "db/sysdb.h"
+#include "util/util.h"
+#include "tools/tools_util.h"
+#include "tools/sss_sync_ops.h"
+
+int main(int argc, const char **argv)
+{
+ int ret = EXIT_SUCCESS;
+ int pc_debug = 0;
+ const char *pc_groupname = NULL;
+ struct tools_ctx *tctx = NULL;
+
+ 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 },
+ POPT_TABLEEND
+ };
+
+ debug_prg_name = argv[0];
+
+ ret = set_locale();
+ if (ret != EOK) {
+ DEBUG(1, ("set_locale failed (%d): %s\n", ret, strerror(ret)));
+ ERROR("Error setting the locale\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* parse ops_ctx */
+ pc = poptGetContext(NULL, argc, argv, long_options, 0);
+ poptSetOtherOptionHelp(pc, "GROUPNAME");
+ if ((ret = poptGetNextOpt(pc)) < -1) {
+ usage(pc, poptStrerror(ret));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ debug_level = pc_debug;
+
+ pc_groupname = poptGetArg(pc);
+ if (pc_groupname == NULL) {
+ usage(pc, _("Specify group to delete\n"));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ CHECK_ROOT(ret, debug_prg_name);
+
+ ret = init_sss_tools(&tctx);
+ if (ret != EOK) {
+ DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
+ if (ret == ENOENT) {
+ ERROR("Error initializing the tools - no local domain\n");
+ } else {
+ ERROR("Error initializing the tools\n");
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* if the domain was not given as part of FQDN, default to local domain */
+ ret = parse_name_domain(tctx, pc_groupname);
+ if (ret != EOK) {
+ ERROR("Invalid domain specified in FQDN\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = sysdb_getgrnam_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;
+ }
+
+ if ((tctx->octx->gid < tctx->local->id_min) ||
+ (tctx->local->id_max && tctx->octx->gid > tctx->local->id_max)) {
+ ERROR("Group %s is outside the defined ID range for domain\n",
+ tctx->octx->name);
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ start_transaction(tctx);
+ if (tctx->error != EOK) {
+ goto done;
+ }
+
+ /* groupdel */
+ ret = groupdel(tctx, tctx->ev, tctx->sysdb, tctx->handle, tctx->octx);
+ if (ret != EOK) {
+ tctx->error = ret;
+
+ /* cancel transaction */
+ talloc_zfree(tctx->handle);
+ goto done;
+ }
+
+ end_transaction(tctx);
+
+ ret = tctx->error;
+done:
+ if (ret) {
+ DEBUG(1, ("sysdb operation failed (%d)[%s]\n", ret, strerror(ret)));
+ switch (ret) {
+ case ENOENT:
+ ERROR("No such group in local domain. "
+ "Removing groups only allowed in local domain.\n");
+ break;
+
+ default:
+ ERROR("Internal error. Could not remove group.\n");
+ break;
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = EXIT_SUCCESS;
+
+fini:
+ talloc_free(tctx);
+ poptFreeContext(pc);
+ exit(ret);
+}
+
diff --git a/src/tools/sss_groupmod.c b/src/tools/sss_groupmod.c
new file mode 100644
index 000000000..b25a018d3
--- /dev/null
+++ b/src/tools/sss_groupmod.c
@@ -0,0 +1,246 @@
+/*
+ SSSD
+
+ sss_groupmod
+
+ Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009
+
+ 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/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <talloc.h>
+#include <popt.h>
+#include <errno.h>
+#include <grp.h>
+#include <unistd.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "tools/tools_util.h"
+#include "tools/sss_sync_ops.h"
+
+int main(int argc, const char **argv)
+{
+ gid_t pc_gid = 0;
+ int pc_debug = 0;
+ 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 },
+ { "append-group", 'a', POPT_ARG_STRING, NULL,
+ 'a', _("Groups to add this group to"), NULL },
+ { "remove-group", 'r', POPT_ARG_STRING, NULL,
+ 'r', _("Groups to remove this group from"), NULL },
+ { "gid", 'g', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_gid,
+ 0, _("The GID of the group"), NULL },
+ POPT_TABLEEND
+ };
+ poptContext pc = NULL;
+ struct tools_ctx *tctx = NULL;
+ char *addgroups = NULL, *rmgroups = NULL;
+ int ret;
+ const char *pc_groupname = NULL;
+ char *badgroup = NULL;
+
+ debug_prg_name = argv[0];
+
+ ret = set_locale();
+ if (ret != EOK) {
+ DEBUG(1, ("set_locale failed (%d): %s\n", ret, strerror(ret)));
+ ERROR("Error setting the locale\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* parse parameters */
+ pc = poptGetContext(NULL, argc, argv, long_options, 0);
+ poptSetOtherOptionHelp(pc, "GROUPNAME");
+ while ((ret = poptGetNextOpt(pc)) > 0) {
+ switch (ret) {
+ case 'a':
+ addgroups = poptGetOptArg(pc);
+ if (addgroups == NULL) {
+ ret = -1;
+ }
+ break;
+
+ case 'r':
+ rmgroups = poptGetOptArg(pc);
+ if (rmgroups == NULL) {
+ ret = -1;
+ }
+ break;
+ }
+ }
+
+ if (ret != -1) {
+ usage(pc, poptStrerror(ret));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* groupname is an argument without --option */
+ pc_groupname = poptGetArg(pc);
+ if (pc_groupname == NULL) {
+ usage(pc, _("Specify group to modify\n"));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ debug_level = pc_debug;
+
+ CHECK_ROOT(ret, debug_prg_name);
+
+ ret = init_sss_tools(&tctx);
+ if (ret != EOK) {
+ DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
+ if (ret == ENOENT) {
+ ERROR("Error initializing the tools - no local domain\n");
+ } else {
+ ERROR("Error initializing the tools\n");
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = parse_name_domain(tctx, pc_groupname);
+ if (ret != EOK) {
+ ERROR("Invalid domain specified in FQDN\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ /* check the username to be able to give sensible error message */
+ ret = sysdb_getgrnam_sync(tctx, tctx->ev, tctx->sysdb,
+ tctx->octx->name, tctx->local,
+ &tctx->octx);
+ if (ret != EOK) {
+ ERROR("Cannot find group in local domain, "
+ "modifying groups is allowed only in local domain\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+
+ tctx->octx->gid = pc_gid;
+
+ if (addgroups) {
+ ret = parse_groups(tctx, addgroups, &tctx->octx->addgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse groups to add the group to\n"));
+ ERROR("Internal error while parsing parameters\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = parse_group_name_domain(tctx, tctx->octx->addgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse FQDN groups to add the group to\n"));
+ ERROR("Member groups must be in the same domain as parent group\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* Check group names in the LOCAL domain */
+ ret = check_group_names(tctx, tctx->octx->addgroups, &badgroup);
+ if (ret != EOK) {
+ ERROR("Cannot find group %s in local domain, "
+ "only groups in local domain are allowed\n", badgroup);
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ }
+
+ if (rmgroups) {
+ ret = parse_groups(tctx, rmgroups, &tctx->octx->rmgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse groups to remove the group from\n"));
+ ERROR("Internal error while parsing parameters\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = parse_group_name_domain(tctx, tctx->octx->rmgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse FQDN groups to remove the group from\n"));
+ ERROR("Member groups must be in the same domain as parent group\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* Check group names in the LOCAL domain */
+ ret = check_group_names(tctx, tctx->octx->rmgroups, &badgroup);
+ if (ret != EOK) {
+ ERROR("Cannot find group %s in local domain, "
+ "only groups in local domain are allowed\n", badgroup);
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ }
+
+ if (id_in_range(tctx->octx->gid, tctx->octx->domain) != EOK) {
+ ERROR("The selected GID is outside the allowed range\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ start_transaction(tctx);
+ if (tctx->error != EOK) {
+ goto done;
+ }
+
+ /* groupmod */
+ ret = groupmod(tctx, tctx->ev, tctx->sysdb, tctx->handle, tctx->octx);
+ if (ret != EOK) {
+ tctx->error = ret;
+
+ /* cancel transaction */
+ talloc_zfree(tctx->handle);
+ goto done;
+ }
+
+ end_transaction(tctx);
+
+done:
+ if (tctx->error) {
+ ret = tctx->error;
+ DEBUG(1, ("sysdb operation failed (%d)[%s]\n", ret, strerror(ret)));
+ switch (ret) {
+ case ENOENT:
+ ERROR("Could not modify group - check if member group names are correct\n");
+ break;
+
+ case EFAULT:
+ ERROR("Could not modify group - check if groupname is correct\n");
+ break;
+
+ default:
+ ERROR("Transaction error. Could not modify group.\n");
+ break;
+ }
+
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = EXIT_SUCCESS;
+
+fini:
+ free(addgroups);
+ free(rmgroups);
+ poptFreeContext(pc);
+ talloc_free(tctx);
+ exit(ret);
+}
diff --git a/src/tools/sss_groupshow.c b/src/tools/sss_groupshow.c
new file mode 100644
index 000000000..2f848b7d8
--- /dev/null
+++ b/src/tools/sss_groupshow.c
@@ -0,0 +1,944 @@
+/*
+ SSSD
+
+ sss_groupshow
+
+ Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2010
+
+ 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/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <talloc.h>
+#include <popt.h>
+
+#include "db/sysdb.h"
+#include "util/util.h"
+#include "tools/tools_util.h"
+#include "tools/sss_sync_ops.h"
+
+#define PADDING_SPACES 4
+#define GROUP_SHOW_ATTRS { SYSDB_MEMBEROF, SYSDB_GIDNUM, \
+ SYSDB_MEMBER, SYSDB_NAME, \
+ NULL }
+#define GROUP_SHOW_MPG_ATTRS { SYSDB_MEMBEROF, SYSDB_UIDNUM, \
+ SYSDB_NAME, NULL }
+
+struct group_info {
+ const char *name;
+ gid_t gid;
+ bool mpg;
+
+ const char **user_members;
+ const char **memberofs;
+
+ struct group_info **group_members;
+};
+
+/*==================Helper routines to process results================= */
+const char *rdn_as_string(TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn)
+{
+ const struct ldb_val *val;
+
+ val = ldb_dn_get_rdn_val(dn);
+ if (val == NULL) {
+ return NULL;
+ }
+
+ return ldb_dn_escape_value(mem_ctx, *val);;
+}
+
+static int parse_memberofs(struct ldb_context *ldb,
+ struct ldb_message_element *el,
+ struct group_info *gi)
+{
+ int i;
+ struct ldb_dn *dn = NULL;
+
+ gi->memberofs = talloc_array(gi, const char *, el->num_values+1);
+ if (gi->memberofs == NULL) {
+ return ENOMEM;
+ }
+
+ for (i = 0; i< el->num_values; ++i) {
+ dn = ldb_dn_from_ldb_val(gi, ldb, &(el->values[i]));
+ gi->memberofs[i] = talloc_strdup(gi, rdn_as_string(gi, dn));
+ talloc_zfree(dn);
+ if (gi->memberofs[i] == NULL) {
+ return ENOMEM;
+ }
+ DEBUG(6, ("memberof value: %s\n", gi->memberofs[i]));
+ }
+ gi->memberofs[el->num_values] = NULL;
+
+ return EOK;
+}
+
+static int parse_members(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ struct sss_domain_info *domain,
+ struct ldb_message_element *el,
+ const char *parent_name,
+ const char ***user_members,
+ const char ***group_members,
+ int *num_group_members)
+{
+ struct ldb_dn *user_basedn = NULL, *group_basedn = NULL;
+ struct ldb_dn *parent_dn = NULL;
+ struct ldb_dn *dn = NULL;
+ const char **um = NULL, **gm = NULL;
+ unsigned int um_index = 0, gm_index = 0;
+ TALLOC_CTX *tmp_ctx = NULL;
+ int ret;
+ int i;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ user_basedn = ldb_dn_new_fmt(tmp_ctx, ldb,
+ SYSDB_TMPL_USER_BASE,
+ domain->name);
+ group_basedn = ldb_dn_new_fmt(tmp_ctx, ldb,
+ SYSDB_TMPL_GROUP_BASE,
+ domain->name);
+ if (!user_basedn || !group_basedn) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ um = talloc_array(mem_ctx, const char *, el->num_values+1);
+ gm = talloc_array(mem_ctx, const char *, el->num_values+1);
+ if (!um || !gm) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ for (i = 0; i< el->num_values; ++i) {
+ dn = ldb_dn_from_ldb_val(tmp_ctx, ldb, &(el->values[i]));
+
+ /* user member or group member? */
+ parent_dn = ldb_dn_get_parent(tmp_ctx, dn);
+ if (ldb_dn_compare_base(parent_dn, user_basedn) == 0) {
+ um[um_index] = rdn_as_string(mem_ctx, dn);
+ if (um[um_index] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ DEBUG(6, ("User member %s\n", um[um_index]));
+ um_index++;
+ } else if (ldb_dn_compare_base(parent_dn, group_basedn) == 0) {
+ gm[gm_index] = rdn_as_string(mem_ctx, dn);
+ if (gm[gm_index] == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ if (parent_name && strcmp(gm[gm_index], parent_name) == 0) {
+ DEBUG(6, ("Skipping circular nesting for group %s\n",
+ gm[gm_index]));
+ continue;
+ }
+ DEBUG(6, ("Group member %s\n", gm[gm_index]));
+ gm_index++;
+ } else {
+ DEBUG(2, ("Group member not a user nor group: %s\n",
+ ldb_dn_get_linearized(dn)));
+ ret = EIO;
+ goto fail;
+ }
+
+ talloc_zfree(dn);
+ talloc_zfree(parent_dn);
+ }
+ um[um_index] = NULL;
+ gm[gm_index] = NULL;
+
+ if (um_index > 0) {
+ um = talloc_realloc(mem_ctx, um, const char *, um_index+1);
+ if (!um) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ } else {
+ talloc_zfree(um);
+ }
+
+ if (gm_index > 0) {
+ gm = talloc_realloc(mem_ctx, gm, const char *, gm_index+1);
+ if (!gm) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ } else {
+ talloc_zfree(gm);
+ }
+
+ *user_members = um;
+ *group_members = gm;
+ *num_group_members = gm_index;
+ talloc_zfree(tmp_ctx);
+ return EOK;
+
+fail:
+ talloc_zfree(um);
+ talloc_zfree(gm);
+ talloc_zfree(tmp_ctx);
+ return ret;
+}
+
+static int process_group(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ struct ldb_message *msg,
+ struct sss_domain_info *domain,
+ const char *parent_name,
+ struct group_info **info,
+ const char ***group_members,
+ int *num_group_members)
+{
+ struct ldb_message_element *el;
+ int ret;
+ struct group_info *gi = NULL;
+
+ DEBUG(6, ("Found entry %s\n", ldb_dn_get_linearized(msg->dn)));
+
+ gi = talloc_zero(mem_ctx, struct group_info);
+ if (!gi) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* mandatory data - name and gid */
+ gi->name = talloc_strdup(gi,
+ ldb_msg_find_attr_as_string(msg,
+ SYSDB_NAME,
+ NULL));
+ gi->gid = ldb_msg_find_attr_as_uint64(msg,
+ SYSDB_GIDNUM, 0);
+ if (gi->gid == 0 || gi->name == NULL) {
+ DEBUG(3, ("No name or no GID?\n"));
+ ret = EIO;
+ goto done;
+ }
+
+ /* list members */
+ el = ldb_msg_find_element(msg, SYSDB_MEMBER);
+ if (el) {
+ ret = parse_members(gi, ldb, domain, el,
+ parent_name,
+ &gi->user_members,
+ group_members, num_group_members);
+ if (ret != EOK) {
+ goto done;
+ }
+ }
+
+ /* list memberofs */
+ el = ldb_msg_find_element(msg, SYSDB_MEMBEROF);
+ if (el) {
+ ret = parse_memberofs(ldb, el, gi);
+ if (ret != EOK) {
+ goto done;
+ }
+ }
+
+ *info = gi;
+ return EOK;
+done:
+ talloc_zfree(gi);
+ return ret;
+}
+
+/*========Find info about a group and recursively about subgroups====== */
+struct group_show_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+ struct sss_domain_info *domain;
+
+ struct group_info *root;
+ bool recursive;
+};
+
+static void group_show_root_done(struct tevent_req *subreq);
+static void group_show_recurse_done(struct tevent_req *subreq);
+
+struct tevent_req *group_show_recurse_send(TALLOC_CTX *,
+ struct group_show_state *,
+ struct group_info *,
+ const char **,
+ const int );
+static int group_show_recurse_recv(TALLOC_CTX *, struct tevent_req *,
+ struct group_info ***);
+
+struct tevent_req *group_show_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ bool recursive,
+ const char *name)
+{
+ struct group_show_state *search_state = NULL;
+ struct tevent_req *subreq = NULL;
+ struct tevent_req *req = NULL;
+ static const char *attrs[] = GROUP_SHOW_ATTRS;
+
+ req = tevent_req_create(mem_ctx, &search_state, struct group_show_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ search_state->ev = ev;
+ search_state->sysdb = sysdb;
+ search_state->handle = handle;
+ search_state->domain = domain;
+ search_state->recursive = recursive;
+
+ /* First, search for the root group */
+ subreq = sysdb_search_group_by_name_send(search_state,
+ search_state->ev,
+ search_state->sysdb,
+ search_state->handle,
+ search_state->domain,
+ name, attrs);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, group_show_root_done, req);
+
+ return req;
+}
+
+static void group_show_root_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct group_show_state *state = tevent_req_data(req,
+ struct group_show_state);
+ int ret;
+ int i;
+ struct ldb_message *msg = NULL;
+ const char **group_members = NULL;
+ int nmembers = 0;
+
+ ret = sysdb_search_group_recv(subreq, state, &msg);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Search failed: %s (%d)\n", strerror(ret), ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ ret = process_group(state,
+ sysdb_ctx_get_ldb(state->sysdb),
+ msg, state->domain, NULL, &state->root,
+ &group_members, &nmembers);
+ if (ret != EOK) {
+ DEBUG(2, ("Group processing failed: %s (%d)\n",
+ strerror(ret), ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (group_members == NULL) {
+ tevent_req_done(req);
+ return;
+ }
+
+ if (state->recursive == false) {
+ /* if not recursive, just fill in names */
+ state->root->group_members = talloc_array(state->root,
+ struct group_info *,
+ nmembers+1);
+ for (i=0; group_members[i]; i++) {
+ state->root->group_members[i] = talloc_zero(state->root,
+ struct group_info);
+ if (!state->root->group_members) {
+ tevent_req_error(req, ENOMEM);
+ }
+ state->root->group_members[i]->name = talloc_strdup(state->root,
+ group_members[i]);
+ if (!state->root->group_members[i]->name) {
+ tevent_req_error(req, ENOMEM);
+ }
+ }
+ state->root->group_members[nmembers] = NULL;
+
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = group_show_recurse_send(state->root, state,
+ state->root,
+ group_members,
+ nmembers);
+ if (!subreq) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ tevent_req_set_callback(subreq, group_show_recurse_done, req);
+}
+
+static void group_show_recurse_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct group_show_state *state = tevent_req_data(req,
+ struct group_show_state);
+ int ret;
+
+ ret = group_show_recurse_recv(state->root,
+ subreq,
+ &state->root->group_members);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Recursive search failed: %s (%d)\n", strerror(ret), ret));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int group_show_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct group_info **res)
+{
+ struct group_show_state *state = tevent_req_data(req,
+ struct group_show_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+ *res = talloc_move(mem_ctx, &state->root);
+
+ return EOK;
+}
+
+/*==================Recursive search for nested groups================= */
+struct group_show_recurse {
+ const char **names;
+ int current;
+
+ struct group_info *parent;
+ struct group_show_state *state;
+
+ struct group_info **groups;
+};
+
+static int group_show_recurse_search(struct tevent_req *,
+ struct group_show_recurse *);
+static void group_show_recurse_next(struct tevent_req *);
+static void group_show_recurse_level_done(struct tevent_req *);
+static void group_show_recurse_cont(struct tevent_req *);
+
+struct tevent_req *group_show_recurse_send(TALLOC_CTX *mem_ctx,
+ struct group_show_state *state,
+ struct group_info *parent,
+ const char **group_members,
+ const int nmembers)
+{
+ struct tevent_req *req = NULL;
+ struct group_show_recurse *recurse_state = NULL;
+
+ req = tevent_req_create(mem_ctx, &recurse_state, struct group_show_recurse);
+ if (req == NULL) {
+ return NULL;
+ }
+ recurse_state->current = 0;
+ recurse_state->parent = parent;
+ recurse_state->names = group_members;
+ recurse_state->state = state;
+ recurse_state->groups = talloc_array(state->root,
+ struct group_info *,
+ nmembers+1); /* trailing NULL */
+
+ if (!recurse_state->names ||
+ !recurse_state->names[recurse_state->current]) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ if (group_show_recurse_search(req, recurse_state) != EOK) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ return req;
+}
+
+static int group_show_recurse_search(struct tevent_req *req,
+ struct group_show_recurse *recurse_state)
+{
+ static const char *attrs[] = GROUP_SHOW_ATTRS;
+ struct tevent_req *subreq = NULL;
+
+ /* Skip circular groups */
+ if (strcmp(recurse_state->names[recurse_state->current],
+ recurse_state->parent->name) == 0) {
+ DEBUG(0, ("CIRCULAR DEP DETECTED\n"));
+ group_show_recurse_cont(req);
+ return EOK;
+ }
+
+ subreq = sysdb_search_group_by_name_send(recurse_state->state,
+ recurse_state->state->ev,
+ recurse_state->state->sysdb,
+ recurse_state->state->handle,
+ recurse_state->state->domain,
+ recurse_state->names[recurse_state->current],
+ attrs);
+ if (!subreq) {
+ return ENOMEM;
+ }
+ tevent_req_set_callback(subreq, group_show_recurse_next, req);
+
+ return EOK;
+}
+
+static void group_show_recurse_next(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct group_show_recurse *recurse_state = tevent_req_data(req,
+ struct group_show_recurse);
+ const char **group_members = NULL;
+ int nmembers = 0;
+ struct ldb_message *msg = NULL;
+ int ret;
+ struct tevent_req *recurse_req = NULL;
+
+ ret = sysdb_search_group_recv(subreq, recurse_state, &msg);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Search failed: %s (%d)\n", strerror(ret), ret));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ ret = process_group(recurse_state->state->root,
+ sysdb_ctx_get_ldb(recurse_state->state->sysdb),
+ msg,
+ recurse_state->state->domain,
+ recurse_state->parent->name,
+ &recurse_state->groups[recurse_state->current],
+ &group_members,
+ &nmembers);
+ if (ret != EOK) {
+ DEBUG(2, ("Group processing failed: %s (%d)\n",
+ strerror(ret), ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ /* descend to another level */
+ if (nmembers > 0) {
+ recurse_req = group_show_recurse_send(recurse_state,
+ recurse_state->state,
+ recurse_state->groups[recurse_state->current],
+ group_members, nmembers);
+ if (!recurse_req) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ /* to free group_members in the callback */
+ group_members = talloc_move(recurse_req, &group_members);
+ tevent_req_set_callback(recurse_req, group_show_recurse_level_done, req);
+ return;
+ }
+
+ /* Move to next group in the same level */
+ group_show_recurse_cont(req);
+}
+
+static void group_show_recurse_level_done(struct tevent_req *recurse_req)
+{
+ int ret;
+ struct tevent_req *req = tevent_req_callback_data(recurse_req,
+ struct tevent_req);
+ struct group_show_recurse *recurse_state = tevent_req_data(recurse_req,
+ struct group_show_recurse);
+
+ ret = group_show_recurse_recv(recurse_state->state->root, recurse_req,
+ &recurse_state->parent->group_members);
+ talloc_zfree(recurse_req);
+ if (ret) {
+ DEBUG(2, ("Recursive search failed: %s (%d)\n", strerror(ret), ret));
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ /* Move to next group on the upper level */
+ group_show_recurse_cont(req);
+}
+
+static void group_show_recurse_cont(struct tevent_req *req)
+{
+ struct group_show_recurse *recurse_state = tevent_req_data(req,
+ struct group_show_recurse);
+ int ret;
+
+ recurse_state->current++;
+ if (recurse_state->names[recurse_state->current] == NULL) {
+ recurse_state->groups[recurse_state->current] = NULL; /* Sentinel */
+ tevent_req_done(req);
+ return;
+ }
+
+ /* examine next group on the same level */
+ ret = group_show_recurse_search(req, recurse_state);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+}
+
+static int group_show_recurse_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct group_info ***out)
+{
+ struct group_show_recurse *recurse_state = tevent_req_data(req,
+ struct group_show_recurse);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+ *out = talloc_move(mem_ctx, &recurse_state->groups);
+
+ return EOK;
+}
+
+/*==================Get info about MPG================================= */
+struct group_show_mpg_state {
+ struct ldb_context *ldb;
+ struct group_info *info;
+};
+
+static void group_show_mpg_done(struct tevent_req *);
+
+struct tevent_req *group_show_mpg_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct sss_domain_info *domain,
+ const char *name)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct group_show_mpg_state *state;
+ static const char *mpg_attrs[] = GROUP_SHOW_MPG_ATTRS;
+
+ req = tevent_req_create(mem_ctx, &state, struct group_show_mpg_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ldb = sysdb_ctx_get_ldb(sysdb);
+
+ subreq = sysdb_search_user_by_name_send(mem_ctx, ev, sysdb, handle,
+ domain, name, mpg_attrs);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, group_show_mpg_done, req);
+
+ return req;
+}
+
+static void group_show_mpg_done(struct tevent_req *subreq)
+{
+ int ret;
+ struct ldb_message *msg = NULL;
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct group_show_mpg_state *state = tevent_req_data(req,
+ struct group_show_mpg_state);
+
+ ret = sysdb_search_user_recv(subreq, req, &msg);
+ talloc_zfree(subreq);
+ if (ret) {
+ DEBUG(2, ("Search failed: %s (%d)\n", strerror(ret), ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ state->info = talloc_zero(state, struct group_info);
+ if (!state->info) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ state->info->name = talloc_strdup(state->info,
+ ldb_msg_find_attr_as_string(msg,
+ SYSDB_NAME,
+ NULL));
+ state->info->gid = ldb_msg_find_attr_as_uint64(msg, SYSDB_UIDNUM, 0);
+ if (state->info->gid == 0 || state->info->name == NULL) {
+ DEBUG(3, ("No name or no GID?\n"));
+ tevent_req_error(req, EIO);
+ return;
+ }
+ state->info->mpg = true;
+
+ tevent_req_done(req);
+}
+
+static int group_show_mpg_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct group_info **res)
+{
+ struct group_show_mpg_state *state = tevent_req_data(req,
+ struct group_show_mpg_state);
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+ *res = talloc_move(mem_ctx, &state->info);
+
+ return EOK;
+}
+
+/*==================The main program=================================== */
+struct sss_groupshow_state {
+ struct group_info *root;
+
+ int ret;
+ bool done;
+};
+
+static void sss_group_show_done(struct tevent_req *req)
+{
+ int ret;
+ struct sss_groupshow_state *sss_state = tevent_req_callback_data(req,
+ struct sss_groupshow_state);
+
+ ret = group_show_recv(sss_state, req, &sss_state->root);
+ talloc_zfree(req);
+
+ sss_state->ret = ret;
+ sss_state->done = true;
+}
+
+static void sss_group_show_mpg_done(struct tevent_req *req)
+{
+ int ret;
+ struct sss_groupshow_state *sss_state = tevent_req_callback_data(req,
+ struct sss_groupshow_state);
+
+ ret = group_show_mpg_recv(sss_state, req, &sss_state->root);
+ talloc_zfree(req);
+
+ sss_state->ret = ret;
+ sss_state->done = true;
+}
+
+static void print_group_info(struct group_info *g, int level)
+{
+ int i;
+ char padding[512];
+ char fmt[8];
+
+ snprintf(fmt, 8, "%%%ds", level*PADDING_SPACES);
+ snprintf(padding, 512, fmt, "");
+
+ printf(_("%s%sGroup: %s\n"), padding,
+ g->mpg ? _("Magic Private ") : "",
+ g->name);
+ printf(_("%sGID number: %d\n"), padding, g->gid);
+
+ printf(_("%sMember users: "), padding);
+ if (g->user_members) {
+ for (i=0; g->user_members[i]; ++i) {
+ printf("%s%s", i>0 ? "," : "",
+ g->user_members[i]);
+ }
+ }
+ printf(_("\n%sIs a member of: "), padding);
+ if (g->memberofs) {
+ for (i=0; g->memberofs[i]; ++i) {
+ printf("%s%s", i>0 ? "," : "",
+ g->memberofs[i]);
+ }
+ }
+ printf(_("\n%sMember groups: "), padding);
+}
+
+static void print_recursive(struct group_info **group_members, int level)
+{
+ int i;
+
+ if (group_members == NULL) {
+ return;
+ }
+
+ level++;
+ for (i=0; group_members[i]; ++i) {
+ printf("\n");
+ print_group_info(group_members[i], level);
+ printf("\n");
+ print_recursive(group_members[i]->group_members, level);
+ }
+}
+
+int main(int argc, const char **argv)
+{
+ int ret = EXIT_SUCCESS;
+ int pc_debug = 0;
+ bool pc_recursive = false;
+ const char *pc_groupname = NULL;
+ struct tools_ctx *tctx = NULL;
+ struct tevent_req *req = NULL;
+ struct sss_groupshow_state *state = NULL;
+ int i;
+
+ 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 },
+ { "recursive", 'R', POPT_ARG_NONE, NULL, 'r',
+ _("Print indirect group members recursively"), NULL },
+ POPT_TABLEEND
+ };
+
+ debug_prg_name = argv[0];
+
+ ret = set_locale();
+ if (ret != EOK) {
+ DEBUG(1, ("set_locale failed (%d): %s\n", ret, strerror(ret)));
+ ERROR("Error setting the locale\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* parse ops_ctx */
+ pc = poptGetContext(NULL, argc, argv, long_options, 0);
+ poptSetOtherOptionHelp(pc, "GROUPNAME");
+ while ((ret = poptGetNextOpt(pc)) > 0) {
+ switch (ret) {
+ case 'r':
+ pc_recursive = true;
+ break;
+ }
+ }
+
+ debug_level = pc_debug;
+
+ if (ret != -1) {
+ usage(pc, poptStrerror(ret));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ pc_groupname = poptGetArg(pc);
+ if (pc_groupname == NULL) {
+ usage(pc, _("Specify group to show\n"));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ CHECK_ROOT(ret, debug_prg_name);
+
+ ret = init_sss_tools(&tctx);
+ if (ret != EOK) {
+ DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
+ if (ret == ENOENT) {
+ ERROR("Error initializing the tools - no local domain\n");
+ } else {
+ ERROR("Error initializing the tools\n");
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* if the domain was not given as part of FQDN, default to local domain */
+ ret = parse_name_domain(tctx, pc_groupname);
+ if (ret != EOK) {
+ ERROR("Invalid domain specified in FQDN\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* The search itself */
+ state = talloc_zero(tctx, struct sss_groupshow_state);
+ if (!state) {
+ goto fini;
+ }
+
+ req = group_show_send(tctx, tctx->ev, tctx->sysdb, tctx->handle,
+ tctx->local, pc_recursive, tctx->octx->name);
+ if (!req) {
+ ERROR("Cannot initiate search\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ tevent_req_set_callback(req, sss_group_show_done, state);
+ while (!state->done) {
+ tevent_loop_once(tctx->ev);
+ }
+ ret = state->ret;
+
+ /* Also show MPGs */
+ if (ret == ENOENT) {
+ state->done = false;
+ state->ret = EOK;
+
+ req = group_show_mpg_send(tctx, tctx->ev, tctx->sysdb, tctx->handle,
+ tctx->local, tctx->octx->name);
+ if (!req) {
+ ERROR("Cannot initiate search\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ tevent_req_set_callback(req, sss_group_show_mpg_done, state);
+ while (!state->done) {
+ tevent_loop_once(tctx->ev);
+ }
+ ret = state->ret;
+ }
+
+ /* Process result */
+ if (ret) {
+ DEBUG(1, ("sysdb operation failed (%d)[%s]\n", ret, strerror(ret)));
+ switch (ret) {
+ case ENOENT:
+ ERROR("No such group in local domain. "
+ "Printing groups only allowed in local domain.\n");
+ break;
+
+ default:
+ ERROR("Internal error. Could not print group.\n");
+ break;
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* print the results */
+ print_group_info(state->root, 0);
+ if (pc_recursive) {
+ printf("\n");
+ print_recursive(state->root->group_members, 0);
+ } else {
+ if (state->root->group_members) {
+ for (i=0; state->root->group_members[i]; ++i) {
+ printf("%s%s", i>0 ? "," : "",
+ state->root->group_members[i]->name);
+ }
+ }
+ printf("\n");
+ }
+
+fini:
+ talloc_free(tctx);
+ poptFreeContext(pc);
+ exit(ret);
+}
diff --git a/src/tools/sss_sync_ops.c b/src/tools/sss_sync_ops.c
new file mode 100644
index 000000000..25b8ac7a5
--- /dev/null
+++ b/src/tools/sss_sync_ops.c
@@ -0,0 +1,1838 @@
+/*
+ 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/>.
+*/
+
+#include <tevent.h>
+#include <talloc.h>
+#include <sys/types.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "tools/sss_sync_ops.h"
+
+/* 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)) { \
+ DEBUG(1, (msg" attribute: %s", attr)); \
+ return val; \
+ } \
+} while(0)
+
+#define SYNC_LOOP(ops, retval) do { \
+ while (!ops->done) { \
+ tevent_loop_once(ev); \
+ } \
+ retval = ops->error; \
+} while(0)
+
+struct sync_op_res {
+ struct ops_ctx *data;
+ int error;
+ bool done;
+};
+
+/*
+ * Generic recv function
+ */
+static int sync_ops_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+/*
+ * Generic add member to group
+ */
+struct add_to_groups_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+
+ int cur;
+ struct ops_ctx *data;
+ struct ldb_dn *member_dn;
+};
+
+static void add_to_groups_done(struct tevent_req *subreq);
+
+static struct tevent_req *add_to_groups_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data,
+ struct ldb_dn *member_dn)
+{
+ struct add_to_groups_state *state;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct ldb_dn *parent_dn;
+
+ req = tevent_req_create(mem_ctx, &state, struct add_to_groups_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->sysdb = sysdb;
+ state->handle = handle;
+ state->data = data;
+ state->member_dn = member_dn;
+ state->cur = 0;
+
+ parent_dn = sysdb_group_dn(state->sysdb, state,
+ state->data->domain->name,
+ state->data->addgroups[state->cur]);
+ if (!parent_dn) {
+ return NULL;
+ }
+
+ subreq = sysdb_mod_group_member_send(state,
+ state->ev,
+ state->handle,
+ member_dn,
+ parent_dn,
+ LDB_FLAG_MOD_ADD);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, add_to_groups_done, req);
+ return req;
+}
+
+static void add_to_groups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct add_to_groups_state *state = tevent_req_data(req,
+ struct add_to_groups_state);
+ int ret;
+ struct ldb_dn *parent_dn;
+ struct tevent_req *next_group_req;
+
+ ret = sysdb_mod_group_member_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ /* go on to next group */
+ state->cur++;
+
+ /* check if we added all of them */
+ if (state->data->addgroups[state->cur] == NULL) {
+ tevent_req_done(req);
+ return;
+ }
+
+ /* if not, schedule a new addition */
+ parent_dn = sysdb_group_dn(state->sysdb, state,
+ state->data->domain->name,
+ state->data->addgroups[state->cur]);
+ if (!parent_dn) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ next_group_req = sysdb_mod_group_member_send(state,
+ state->ev,
+ state->handle,
+ state->member_dn,
+ parent_dn,
+ LDB_FLAG_MOD_ADD);
+ if (!next_group_req) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(next_group_req, add_to_groups_done, req);
+}
+
+static int add_to_groups_recv(struct tevent_req *req)
+{
+ return sync_ops_recv(req);
+}
+
+/*
+ * Generic remove member from group
+ */
+struct remove_from_groups_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+
+ int cur;
+ struct ops_ctx *data;
+ struct ldb_dn *member_dn;
+};
+
+static void remove_from_groups_done(struct tevent_req *subreq);
+
+static struct tevent_req *remove_from_groups_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data,
+ struct ldb_dn *member_dn)
+{
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct ldb_dn *parent_dn;
+ struct remove_from_groups_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct remove_from_groups_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->sysdb = sysdb;
+ state->handle = handle;
+ state->data = data;
+ state->member_dn = member_dn;
+ state->cur = 0;
+
+ parent_dn = sysdb_group_dn(state->sysdb, state,
+ state->data->domain->name,
+ state->data->rmgroups[state->cur]);
+ if (!parent_dn) {
+ return NULL;
+ }
+
+ subreq = sysdb_mod_group_member_send(state,
+ state->ev,
+ state->handle,
+ state->member_dn,
+ parent_dn,
+ LDB_FLAG_MOD_DELETE);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, remove_from_groups_done, req);
+ return req;
+}
+
+static void remove_from_groups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct remove_from_groups_state *state = tevent_req_data(req,
+ struct remove_from_groups_state);
+ int ret;
+ struct ldb_dn *parent_dn;
+ struct tevent_req *next_group_req;
+
+ ret = sysdb_mod_group_member_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ /* go on to next group */
+ state->cur++;
+
+ /* check if we removed all of them */
+ if (state->data->rmgroups[state->cur] == NULL) {
+ tevent_req_done(req);
+ return;
+ }
+
+ /* if not, schedule a new removal */
+ parent_dn = sysdb_group_dn(state->sysdb, state,
+ state->data->domain->name,
+ state->data->rmgroups[state->cur]);
+ if (!parent_dn) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ next_group_req = sysdb_mod_group_member_send(state,
+ state->ev,
+ state->handle,
+ state->member_dn,
+ parent_dn,
+ LDB_FLAG_MOD_DELETE);
+ if (!next_group_req) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(next_group_req, remove_from_groups_done, req);
+}
+
+static int remove_from_groups_recv(struct tevent_req *req)
+{
+ return sync_ops_recv(req);
+}
+
+/*
+ * Add a user
+ */
+struct user_add_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+
+ struct ops_ctx *data;
+};
+
+static void user_add_to_group_done(struct tevent_req *groupreq);
+static void user_add_done(struct tevent_req *subreq);
+
+static struct tevent_req *user_add_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ struct user_add_state *state = NULL;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+
+ req = tevent_req_create(mem_ctx, &state, struct user_add_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->sysdb = sysdb;
+ state->handle = handle;
+ state->data = data;
+
+ subreq = sysdb_add_user_send(state, state->ev, state->handle,
+ state->data->domain, state->data->name,
+ state->data->uid, state->data->gid,
+ state->data->gecos, state->data->home,
+ state->data->shell, NULL, 0);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, user_add_done, req);
+ return req;
+}
+
+static void user_add_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct user_add_state *state = tevent_req_data(req,
+ struct user_add_state);
+ int ret;
+ struct ldb_dn *member_dn;
+ struct tevent_req *groupreq;
+
+ ret = sysdb_add_user_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->data->addgroups) {
+ member_dn = sysdb_user_dn(state->sysdb, state,
+ state->data->domain->name,
+ state->data->name);
+ if (!member_dn) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ groupreq = add_to_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, member_dn);
+ tevent_req_set_callback(groupreq, user_add_to_group_done, req);
+ return;
+ }
+
+ return tevent_req_done(req);
+}
+
+static void user_add_to_group_done(struct tevent_req *groupreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(groupreq,
+ struct tevent_req);
+ int ret;
+
+ ret = add_to_groups_recv(groupreq);
+ talloc_zfree(groupreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static int user_add_recv(struct tevent_req *req)
+{
+ return sync_ops_recv(req);
+}
+
+/*
+ * Remove a user
+ */
+struct user_del_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+
+ struct ops_ctx *data;
+};
+
+static void user_del_done(struct tevent_req *subreq);
+
+static struct tevent_req *user_del_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ struct user_del_state *state = NULL;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct ldb_dn *user_dn;
+
+ req = tevent_req_create(mem_ctx, &state, struct user_del_state);
+ if (req == NULL) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ state->ev = ev;
+ state->sysdb = sysdb;
+ state->handle = handle;
+ state->data = data;
+
+ user_dn = sysdb_user_dn(state->sysdb, state,
+ state->data->domain->name, state->data->name);
+ if (!user_dn) {
+ DEBUG(1, ("Could not construct a user DN\n"));
+ return NULL;
+ }
+
+ subreq = sysdb_delete_entry_send(state,
+ state->ev, state->handle,
+ user_dn, false);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, user_del_done, req);
+ return req;
+}
+
+static void user_del_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_delete_entry_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ return tevent_req_done(req);
+}
+
+static int user_del_recv(struct tevent_req *req)
+{
+ return sync_ops_recv(req);
+}
+
+/*
+ * Modify a user
+ */
+struct user_mod_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+
+ struct sysdb_attrs *attrs;
+ struct ldb_dn *member_dn;
+
+ struct ops_ctx *data;
+};
+
+static int usermod_build_attrs(TALLOC_CTX *mem_ctx,
+ const char *gecos,
+ const char *home,
+ const char *shell,
+ uid_t uid,
+ gid_t gid,
+ int lock,
+ struct sysdb_attrs **_attrs)
+{
+ int ret;
+ struct sysdb_attrs *attrs;
+
+ attrs = sysdb_new_attrs(mem_ctx);
+ if (attrs == NULL) {
+ return ENOMEM;
+ }
+
+ if (shell) {
+ ret = sysdb_attrs_add_string(attrs,
+ SYSDB_SHELL,
+ shell);
+ VAR_CHECK(ret, EOK, SYSDB_SHELL,
+ "Could not add attribute to changeset\n");
+ }
+
+ if (home) {
+ ret = sysdb_attrs_add_string(attrs,
+ SYSDB_HOMEDIR,
+ home);
+ VAR_CHECK(ret, EOK, SYSDB_HOMEDIR,
+ "Could not add attribute to changeset\n");
+ }
+
+ if (gecos) {
+ ret = sysdb_attrs_add_string(attrs,
+ SYSDB_GECOS,
+ gecos);
+ VAR_CHECK(ret, EOK, SYSDB_GECOS,
+ "Could not add attribute to changeset\n");
+ }
+
+ if (uid) {
+ ret = sysdb_attrs_add_long(attrs,
+ SYSDB_UIDNUM,
+ uid);
+ VAR_CHECK(ret, EOK, SYSDB_UIDNUM,
+ "Could not add attribute to changeset\n");
+ }
+
+ if (gid) {
+ ret = sysdb_attrs_add_long(attrs,
+ SYSDB_GIDNUM,
+ gid);
+ VAR_CHECK(ret, EOK, SYSDB_GIDNUM,
+ "Could not add attribute to changeset\n");
+ }
+
+ if (lock == DO_LOCK) {
+ ret = sysdb_attrs_add_string(attrs,
+ SYSDB_DISABLED,
+ "true");
+ VAR_CHECK(ret, EOK, SYSDB_DISABLED,
+ "Could not add attribute to changeset\n");
+ }
+
+ if (lock == DO_UNLOCK) {
+ /* PAM code checks for 'false' value in SYSDB_DISABLED attribute */
+ ret = sysdb_attrs_add_string(attrs,
+ SYSDB_DISABLED,
+ "false");
+ VAR_CHECK(ret, EOK, SYSDB_DISABLED,
+ "Could not add attribute to changeset\n");
+ }
+
+ *_attrs = attrs;
+ return EOK;
+}
+
+static void user_mod_attr_done(struct tevent_req *attrreq);
+static void user_mod_attr_wakeup(struct tevent_req *subreq);
+static void user_mod_rm_group_done(struct tevent_req *groupreq);
+static void user_mod_add_group_done(struct tevent_req *groupreq);
+
+static struct tevent_req *user_mod_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ struct user_mod_state *state = NULL;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ int ret;
+ struct timeval tv = { 0, 0 };
+
+ req = tevent_req_create(mem_ctx, &state, struct user_mod_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->sysdb = sysdb;
+ state->handle = handle;
+ state->data = data;
+
+ if (data->addgroups || data->rmgroups) {
+ state->member_dn = sysdb_user_dn(state->sysdb, state,
+ state->data->domain->name,
+ state->data->name);
+ if (!state->member_dn) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ }
+
+ ret = usermod_build_attrs(state,
+ state->data->gecos,
+ state->data->home,
+ state->data->shell,
+ state->data->uid,
+ state->data->gid,
+ state->data->lock,
+ &state->attrs);
+ if (ret != EOK) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ subreq = tevent_wakeup_send(req, ev, tv);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, user_mod_attr_wakeup, req);
+ return req;
+}
+
+static void user_mod_attr_wakeup(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct user_mod_state *state = tevent_req_data(req,
+ struct user_mod_state);
+ struct tevent_req *attrreq, *groupreq;
+
+ if (state->attrs->num != 0) {
+ attrreq = sysdb_set_user_attr_send(state, state->ev, state->handle,
+ state->data->domain, state->data->name,
+ state->attrs, SYSDB_MOD_REP);
+ if (!attrreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(attrreq, user_mod_attr_done, req);
+ return;
+ }
+
+ if (state->data->rmgroups != NULL) {
+ groupreq = remove_from_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!groupreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(groupreq, user_mod_rm_group_done, req);
+ return;
+ }
+
+ if (state->data->addgroups != NULL) {
+ groupreq = add_to_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!groupreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(groupreq, user_mod_add_group_done, req);
+ return;
+ }
+
+ /* No changes to be made, mark request as done */
+ tevent_req_done(req);
+}
+
+static void user_mod_attr_done(struct tevent_req *attrreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(attrreq,
+ struct tevent_req);
+ struct user_mod_state *state = tevent_req_data(req,
+ struct user_mod_state);
+ int ret;
+ struct tevent_req *groupreq;
+
+ ret = sysdb_set_user_attr_recv(attrreq);
+ talloc_zfree(attrreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->data->rmgroups != NULL) {
+ groupreq = remove_from_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!groupreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(groupreq, user_mod_rm_group_done, req);
+ return;
+ }
+
+ if (state->data->addgroups != NULL) {
+ groupreq = add_to_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!groupreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(groupreq, user_mod_add_group_done, req);
+ return;
+ }
+
+ return tevent_req_done(req);
+}
+
+static void user_mod_rm_group_done(struct tevent_req *groupreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(groupreq,
+ struct tevent_req);
+ struct user_mod_state *state = tevent_req_data(req,
+ struct user_mod_state);
+ int ret;
+ struct tevent_req *addreq;
+
+ ret = remove_from_groups_recv(groupreq);
+ talloc_zfree(groupreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->data->addgroups != NULL) {
+ addreq = add_to_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!addreq) {
+ tevent_req_error(req, ENOMEM);
+ }
+ tevent_req_set_callback(addreq, user_mod_add_group_done, req);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static void user_mod_add_group_done(struct tevent_req *groupreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(groupreq,
+ struct tevent_req);
+ int ret;
+
+ ret = add_to_groups_recv(groupreq);
+ talloc_zfree(groupreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static int user_mod_recv(struct tevent_req *req)
+{
+ return sync_ops_recv(req);
+}
+
+/*
+ * Add a group
+ */
+struct group_add_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+ struct sysdb_attrs *attrs;
+
+ struct ops_ctx *data;
+};
+
+static void group_add_done(struct tevent_req *subreq);
+
+static struct tevent_req *group_add_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ struct group_add_state *state = NULL;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+
+ req = tevent_req_create(mem_ctx, &state, struct group_add_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->sysdb = sysdb;
+ state->handle = handle;
+ state->data = data;
+
+ subreq = sysdb_add_group_send(state, state->ev, state->handle,
+ state->data->domain, state->data->name,
+ state->data->gid, NULL, 0);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, group_add_done, req);
+ return req;
+}
+
+static void group_add_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_add_group_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ return tevent_req_done(req);
+}
+
+static int group_add_recv(struct tevent_req *req)
+{
+ return sync_ops_recv(req);
+}
+
+/*
+ * Delete a group
+ */
+struct group_del_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+ struct sysdb_attrs *attrs;
+
+ struct ops_ctx *data;
+};
+
+static void group_del_done(struct tevent_req *subreq);
+
+static struct tevent_req *group_del_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ struct group_del_state *state = NULL;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct ldb_dn *group_dn;
+
+ req = tevent_req_create(mem_ctx, &state, struct group_del_state);
+ if (req == NULL) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ state->ev = ev;
+ state->sysdb = sysdb;
+ state->handle = handle;
+ state->data = data;
+
+ group_dn = sysdb_group_dn(state->sysdb, state,
+ state->data->domain->name, state->data->name);
+ if (group_dn == NULL) {
+ DEBUG(1, ("Could not construct a group DN\n"));
+ return NULL;
+ }
+
+ subreq = sysdb_delete_entry_send(state,
+ state->ev, state->handle,
+ group_dn, false);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, group_del_done, req);
+ return req;
+}
+
+static void group_del_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ int ret;
+
+ ret = sysdb_delete_entry_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ return tevent_req_done(req);
+}
+
+static int group_del_recv(struct tevent_req *req)
+{
+ return sync_ops_recv(req);
+}
+
+/*
+ * Modify a group
+ */
+struct group_mod_state {
+ struct tevent_context *ev;
+ struct sysdb_ctx *sysdb;
+ struct sysdb_handle *handle;
+
+ struct sysdb_attrs *attrs;
+ struct ldb_dn *member_dn;
+
+ struct ops_ctx *data;
+};
+
+static void group_mod_attr_done(struct tevent_req *);
+static void group_mod_attr_wakeup(struct tevent_req *);
+static void group_mod_add_group_done(struct tevent_req *groupreq);
+static void group_mod_rm_group_done(struct tevent_req *groupreq);
+
+static struct tevent_req *group_mod_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ struct group_mod_state *state;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct timeval tv = { 0, 0 };
+
+ req = tevent_req_create(mem_ctx, &state, struct group_mod_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->sysdb = sysdb;
+ state->handle = handle;
+ state->data = data;
+
+ if (data->addgroups || data->rmgroups) {
+ state->member_dn = sysdb_group_dn(state->sysdb, state,
+ state->data->domain->name,
+ state->data->name);
+ if (!state->member_dn) {
+ return NULL;
+ }
+ }
+
+ subreq = tevent_wakeup_send(req, ev, tv);
+ if (!subreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, group_mod_attr_wakeup, req);
+ return req;
+}
+
+static void group_mod_attr_wakeup(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct group_mod_state *state = tevent_req_data(req,
+ struct group_mod_state);
+ struct sysdb_attrs *attrs;
+ struct tevent_req *attrreq;
+ struct tevent_req *groupreq;
+ int ret;
+
+ if (state->data->gid != 0) {
+ attrs = sysdb_new_attrs(NULL);
+ if (!attrs) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ ret = sysdb_attrs_add_uint32(attrs, SYSDB_GIDNUM, state->data->gid);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ attrreq = sysdb_set_group_attr_send(state, state->ev, state->handle,
+ state->data->domain, state->data->name,
+ attrs, SYSDB_MOD_REP);
+ if (!attrreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ tevent_req_set_callback(attrreq, group_mod_attr_done, req);
+ return;
+ }
+
+ if (state->data->rmgroups != NULL) {
+ groupreq = remove_from_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!groupreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(groupreq, group_mod_rm_group_done, req);
+ return;
+ }
+
+ if (state->data->addgroups != NULL) {
+ groupreq = add_to_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!groupreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(groupreq, group_mod_add_group_done, req);
+ return;
+ }
+
+ /* No changes to be made, mark request as done */
+ tevent_req_done(req);
+}
+
+static void group_mod_attr_done(struct tevent_req *attrreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(attrreq,
+ struct tevent_req);
+ struct group_mod_state *state = tevent_req_data(req,
+ struct group_mod_state);
+ int ret;
+ struct tevent_req *groupreq;
+
+ ret = sysdb_set_group_attr_recv(attrreq);
+ talloc_zfree(attrreq);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->data->rmgroups != NULL) {
+ groupreq = remove_from_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!groupreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(groupreq, group_mod_rm_group_done, req);
+ return;
+ }
+
+ if (state->data->addgroups != NULL) {
+ groupreq = add_to_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!groupreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(groupreq, group_mod_add_group_done, req);
+ return;
+ }
+
+ return tevent_req_done(req);
+}
+
+static void group_mod_rm_group_done(struct tevent_req *groupreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(groupreq,
+ struct tevent_req);
+ struct group_mod_state *state = tevent_req_data(req,
+ struct group_mod_state);
+ int ret;
+ struct tevent_req *addreq;
+
+ ret = remove_from_groups_recv(groupreq);
+ talloc_zfree(groupreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (state->data->addgroups != NULL) {
+ addreq = add_to_groups_send(state, state->ev, state->sysdb,
+ state->handle, state->data, state->member_dn);
+ if (!addreq) {
+ tevent_req_error(req, ENOMEM);
+ }
+ tevent_req_set_callback(addreq, group_mod_add_group_done, req);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static void group_mod_add_group_done(struct tevent_req *groupreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(groupreq,
+ struct tevent_req);
+ int ret;
+
+ ret = add_to_groups_recv(groupreq);
+ talloc_zfree(groupreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+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
+ */
+int useradd_defaults(TALLOC_CTX *mem_ctx,
+ struct confdb_ctx *confdb,
+ struct ops_ctx *data,
+ const char *gecos,
+ const char *homedir,
+ const char *shell,
+ int create_home,
+ const char *skeldir)
+{
+ int ret;
+ char *basedir = NULL;
+ char *conf_path = NULL;
+
+ conf_path = talloc_asprintf(mem_ctx, CONFDB_DOMAIN_PATH_TMPL, data->domain->name);
+ if (!conf_path) {
+ 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);
+ } else {
+ ret = confdb_get_string(confdb, mem_ctx,
+ conf_path, CONFDB_LOCAL_DEFAULT_BASEDIR,
+ DFL_BASEDIR_VAL, &basedir);
+ if (ret != EOK) {
+ goto done;
+ }
+ data->home = talloc_asprintf(mem_ctx, "%s/%s", basedir, data->name);
+ }
+ 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, &data->shell);
+ if (ret != EOK) {
+ goto done;
+ }
+ } else {
+ data->shell = talloc_strdup(mem_ctx, shell);
+ if (!data->shell) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+ 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(basedir);
+ talloc_free(conf_path);
+ return ret;
+}
+
+/*
+ * Public interface for adding users
+ */
+static void useradd_done(struct tevent_req *);
+
+int useradd(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ int ret;
+ struct tevent_req *req;
+ struct sync_op_res *res = NULL;
+
+ res = talloc_zero(mem_ctx, struct sync_op_res);
+ if (!res) {
+ return ENOMEM;
+ }
+
+ req = user_add_send(res, ev, sysdb, handle, data);
+ if (!req) {
+ return ENOMEM;
+ }
+ tevent_req_set_callback(req, useradd_done, res);
+
+ SYNC_LOOP(res, ret);
+
+ talloc_free(res);
+ return ret;
+}
+
+static void useradd_done(struct tevent_req *req)
+{
+ int ret;
+ struct sync_op_res *res = tevent_req_callback_data(req,
+ struct sync_op_res);
+
+ ret = user_add_recv(req);
+ talloc_free(req);
+ if (ret) {
+ DEBUG(2, ("Adding user failed: %s (%d)\n", strerror(ret), ret));
+ }
+
+ res->done = true;
+ res->error = ret;
+}
+
+/*
+ * Public interface for deleting users
+ */
+static void userdel_done(struct tevent_req *req);
+
+int userdel(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ int ret;
+ struct tevent_req *req;
+ struct sync_op_res *res = NULL;
+
+ res = talloc_zero(mem_ctx, struct sync_op_res);
+ if (!res) {
+ return ENOMEM;
+ }
+
+ req = user_del_send(res, ev, sysdb, handle, data);
+ if (!req) {
+ return ENOMEM;
+ }
+ tevent_req_set_callback(req, userdel_done, res);
+
+ SYNC_LOOP(res, ret);
+
+ talloc_free(res);
+ return ret;
+}
+
+static void userdel_done(struct tevent_req *req)
+{
+ int ret;
+ struct sync_op_res *res = tevent_req_callback_data(req,
+ struct sync_op_res);
+
+ ret = user_del_recv(req);
+ talloc_free(req);
+ if (ret) {
+ DEBUG(2, ("Removing user failed: %s (%d)\n", strerror(ret), ret));
+ }
+
+ res->done = true;
+ res->error = ret;
+}
+
+/*
+ * Public interface for modifying users
+ */
+static void usermod_done(struct tevent_req *req);
+
+int usermod(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ int ret;
+ struct tevent_req *req;
+ struct sync_op_res *res = NULL;
+
+ res = talloc_zero(mem_ctx, struct sync_op_res);
+ if (!res) {
+ return ENOMEM;
+ }
+
+ req = user_mod_send(res, ev, sysdb, handle, data);
+ if (!req) {
+ return ENOMEM;
+ }
+ tevent_req_set_callback(req, usermod_done, res);
+
+ SYNC_LOOP(res, ret);
+
+ talloc_free(res);
+ return ret;
+}
+
+static void usermod_done(struct tevent_req *req)
+{
+ int ret;
+ struct sync_op_res *res = tevent_req_callback_data(req,
+ struct sync_op_res);
+
+ ret = user_mod_recv(req);
+ talloc_free(req);
+ if (ret) {
+ DEBUG(2, ("Modifying user failed: %s (%d)\n", strerror(ret), ret));
+ }
+
+ res->done = true;
+ res->error = ret;
+}
+
+/*
+ * Public interface for adding groups
+ */
+static void groupadd_done(struct tevent_req *);
+
+int groupadd(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ int ret;
+ struct tevent_req *req;
+ struct sync_op_res *res = NULL;
+
+ res = talloc_zero(mem_ctx, struct sync_op_res);
+ if (!res) {
+ return ENOMEM;
+ }
+
+ req = group_add_send(res, ev, sysdb, handle, data);
+ if (!req) {
+ return ENOMEM;
+ }
+ tevent_req_set_callback(req, groupadd_done, res);
+
+ SYNC_LOOP(res, ret);
+
+ talloc_free(res);
+ return ret;
+}
+
+static void groupadd_done(struct tevent_req *req)
+{
+ int ret;
+ struct sync_op_res *res = tevent_req_callback_data(req,
+ struct sync_op_res);
+
+ ret = group_add_recv(req);
+ talloc_free(req);
+ if (ret) {
+ DEBUG(2, ("Adding group failed: %s (%d)\n", strerror(ret), ret));
+ }
+
+ res->done = true;
+ res->error = ret;
+}
+
+/*
+ * Public interface for deleting groups
+ */
+static void groupdel_done(struct tevent_req *req);
+
+int groupdel(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ int ret;
+ struct tevent_req *req;
+ struct sync_op_res *res = NULL;
+
+ res = talloc_zero(mem_ctx, struct sync_op_res);
+ if (!res) {
+ return ENOMEM;
+ }
+
+ req = group_del_send(res, ev, sysdb, handle, data);
+ if (!req) {
+ return ENOMEM;
+ }
+ tevent_req_set_callback(req, groupdel_done, res);
+
+ SYNC_LOOP(res, ret);
+
+ talloc_free(res);
+ return ret;
+}
+
+static void groupdel_done(struct tevent_req *req)
+{
+ int ret;
+ struct sync_op_res *res = tevent_req_callback_data(req,
+ struct sync_op_res);
+
+ ret = group_del_recv(req);
+ talloc_free(req);
+ if (ret) {
+ DEBUG(2, ("Removing group failed: %s (%d)\n", strerror(ret), ret));
+ }
+
+ res->done = true;
+ res->error = ret;
+}
+
+/*
+ * Public interface for modifying groups
+ */
+static void groupmod_done(struct tevent_req *req);
+
+int groupmod(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data)
+{
+ int ret;
+ struct tevent_req *req;
+ struct sync_op_res *res = NULL;
+
+ res = talloc_zero(mem_ctx, struct sync_op_res);
+ if (!res) {
+ return ENOMEM;
+ }
+
+ req = group_mod_send(res, ev, sysdb, handle, data);
+ if (!req) {
+ return ENOMEM;
+ }
+ tevent_req_set_callback(req, groupmod_done, res);
+
+ SYNC_LOOP(res, ret);
+
+ talloc_free(res);
+ return ret;
+}
+
+static void groupmod_done(struct tevent_req *req)
+{
+ int ret;
+ struct sync_op_res *res = tevent_req_callback_data(req,
+ struct sync_op_res);
+
+ ret = group_mod_recv(req);
+ talloc_free(req);
+ if (ret) {
+ DEBUG(2, ("Modifying group failed: %s (%d)\n", strerror(ret), ret));
+ }
+
+ res->done = true;
+ res->error = ret;
+}
+
+/*
+ * Synchronous transaction functions
+ */
+static void start_transaction_done(struct tevent_req *req);
+
+void start_transaction(struct tools_ctx *tctx)
+{
+ struct tevent_req *req;
+
+ /* make sure handle is NULL, as it is the spy to check if the transaction
+ * has been started */
+ tctx->handle = NULL;
+ tctx->error = 0;
+
+ req = sysdb_transaction_send(tctx->octx, tctx->ev, tctx->sysdb);
+ if (!req) {
+ DEBUG(1, ("Could not start transaction\n"));
+ tctx->error = ENOMEM;
+ return;
+ }
+ tevent_req_set_callback(req, start_transaction_done, tctx);
+
+ /* loop to obtain a transaction */
+ while (!tctx->handle && !tctx->error) {
+ tevent_loop_once(tctx->ev);
+ }
+}
+
+static void start_transaction_done(struct tevent_req *req)
+{
+ struct tools_ctx *tctx = tevent_req_callback_data(req,
+ struct tools_ctx);
+ int ret;
+
+ ret = sysdb_transaction_recv(req, tctx, &tctx->handle);
+ if (ret) {
+ tctx->error = ret;
+ }
+ if (!tctx->handle) {
+ tctx->error = EIO;
+ }
+ talloc_zfree(req);
+}
+
+static void end_transaction_done(struct tevent_req *req);
+
+void end_transaction(struct tools_ctx *tctx)
+{
+ struct tevent_req *req;
+
+ tctx->error = 0;
+
+ req = sysdb_transaction_commit_send(tctx, tctx->ev, tctx->handle);
+ if (!req) {
+ /* free transaction and signal error */
+ tctx->error = ENOMEM;
+ return;
+ }
+ tevent_req_set_callback(req, end_transaction_done, tctx);
+
+ /* loop to obtain a transaction */
+ while (!tctx->transaction_done && !tctx->error) {
+ tevent_loop_once(tctx->ev);
+ }
+}
+
+static void end_transaction_done(struct tevent_req *req)
+{
+ struct tools_ctx *tctx = tevent_req_callback_data(req,
+ struct tools_ctx);
+ int ret;
+
+ ret = sysdb_transaction_commit_recv(req);
+
+ tctx->transaction_done = true;
+ tctx->error = ret;
+ talloc_zfree(req);
+}
+
+/*
+ * getpwnam, getgrnam and friends
+ */
+static void sss_getpwnam_done(void *ptr, int status,
+ struct ldb_result *lrs);
+
+int sysdb_getpwnam_sync(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ const char *name,
+ struct sss_domain_info *domain,
+ struct ops_ctx **out)
+{
+ int ret;
+ struct sync_op_res *res = NULL;
+
+ res = talloc_zero(mem_ctx, struct sync_op_res);
+ if (!res) {
+ return ENOMEM;
+ }
+
+ if (out == NULL) {
+ DEBUG(1, ("NULL passed for storage pointer\n"));
+ return EINVAL;
+ }
+ res->data = *out;
+
+ ret = sysdb_getpwnam(mem_ctx,
+ sysdb,
+ domain,
+ name,
+ sss_getpwnam_done,
+ res);
+
+ SYNC_LOOP(res, ret);
+
+ return ret;
+}
+
+static void sss_getpwnam_done(void *ptr, int status,
+ struct ldb_result *lrs)
+{
+ struct sync_op_res *res = talloc_get_type(ptr, struct sync_op_res );
+ const char *str;
+
+ res->done = true;
+
+ if (status != LDB_SUCCESS) {
+ res->error = status;
+ return;
+ }
+
+ switch (lrs->count) {
+ case 0:
+ DEBUG(1, ("No result for sysdb_getpwnam call\n"));
+ res->error = ENOENT;
+ break;
+
+ case 1:
+ res->error = EOK;
+ /* fill ops_ctx */
+ res->data->uid = ldb_msg_find_attr_as_uint64(lrs->msgs[0],
+ SYSDB_UIDNUM, 0);
+
+ res->data->gid = ldb_msg_find_attr_as_uint64(lrs->msgs[0],
+ SYSDB_GIDNUM, 0);
+
+ str = ldb_msg_find_attr_as_string(lrs->msgs[0],
+ SYSDB_NAME, NULL);
+ res->data->name = talloc_strdup(res, str);
+ if (res->data->name == NULL) {
+ res->error = ENOMEM;
+ return;
+ }
+
+ str = ldb_msg_find_attr_as_string(lrs->msgs[0],
+ SYSDB_GECOS, NULL);
+ res->data->gecos = talloc_strdup(res, str);
+ if (res->data->gecos == NULL) {
+ res->error = ENOMEM;
+ return;
+ }
+
+ str = ldb_msg_find_attr_as_string(lrs->msgs[0],
+ SYSDB_HOMEDIR, NULL);
+ res->data->home = talloc_strdup(res, str);
+ if (res->data->home == NULL) {
+ res->error = ENOMEM;
+ return;
+ }
+
+ str = ldb_msg_find_attr_as_string(lrs->msgs[0],
+ SYSDB_SHELL, NULL);
+ res->data->shell = talloc_strdup(res, str);
+ if (res->data->shell == NULL) {
+ res->error = ENOMEM;
+ return;
+ }
+
+ str = ldb_msg_find_attr_as_string(lrs->msgs[0],
+ SYSDB_DISABLED, NULL);
+ if (str == NULL) {
+ res->data->lock = DO_UNLOCK;
+ } else {
+ if (strcasecmp(str, "true") == 0) {
+ res->data->lock = DO_LOCK;
+ } else if (strcasecmp(str, "false") == 0) {
+ res->data->lock = DO_UNLOCK;
+ } else { /* Invalid value */
+ DEBUG(2, ("Invalid value for %s attribute: %s\n",
+ SYSDB_DISABLED, str ? str : "NULL"));
+ res->error = EIO;
+ return;
+ }
+ }
+ break;
+
+ default:
+ DEBUG(1, ("More than one result for sysdb_getpwnam call\n"));
+ res->error = EIO;
+ break;
+ }
+}
+
+static void sss_getgrnam_done(void *ptr, int status,
+ struct ldb_result *lrs);
+
+int sysdb_getgrnam_sync(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ const char *name,
+ struct sss_domain_info *domain,
+ struct ops_ctx **out)
+{
+ int ret;
+ struct sync_op_res *res = NULL;
+
+ res = talloc_zero(mem_ctx, struct sync_op_res);
+ if (!res) {
+ return ENOMEM;
+ }
+
+ if (out == NULL) {
+ DEBUG(1, ("NULL passed for storage pointer\n"));
+ return EINVAL;
+ }
+ res->data = *out;
+
+ ret = sysdb_getgrnam(mem_ctx,
+ sysdb,
+ domain,
+ name,
+ sss_getgrnam_done,
+ res);
+
+ SYNC_LOOP(res, ret);
+
+ return ret;
+}
+
+static void sss_getgrnam_done(void *ptr, int status,
+ struct ldb_result *lrs)
+{
+ struct sync_op_res *res = talloc_get_type(ptr, struct sync_op_res );
+ const char *str;
+
+ res->done = true;
+
+ if (status != LDB_SUCCESS) {
+ res->error = status;
+ return;
+ }
+
+ switch (lrs->count) {
+ case 0:
+ DEBUG(1, ("No result for sysdb_getgrnam call\n"));
+ res->error = ENOENT;
+ break;
+
+ /* sysdb_getgrnam also returns members */
+ default:
+ res->error = EOK;
+ /* fill ops_ctx */
+ res->data->gid = ldb_msg_find_attr_as_uint64(lrs->msgs[0],
+ SYSDB_GIDNUM, 0);
+ str = ldb_msg_find_attr_as_string(lrs->msgs[0],
+ SYSDB_NAME, NULL);
+ res->data->name = talloc_strdup(res, str);
+ if (res->data->name == NULL) {
+ res->error = ENOMEM;
+ return;
+ }
+ break;
+ }
+}
+
diff --git a/src/tools/sss_sync_ops.h b/src/tools/sss_sync_ops.h
new file mode 100644
index 000000000..383319a8f
--- /dev/null
+++ b/src/tools/sss_sync_ops.h
@@ -0,0 +1,125 @@
+/*
+ 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/>.
+*/
+
+#ifndef __SSS_OPS_H__
+#define __SSS_OPS_H__
+
+#include "tools/tools_util.h"
+#include <stdbool.h>
+
+#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;
+
+ char *name;
+ uid_t uid;
+ gid_t gid;
+ char *gecos;
+ char *home;
+ char *shell;
+ int lock;
+
+ bool create_homedir;
+ bool remove_homedir;
+ mode_t umask;
+ char *skeldir;
+ char *maildir;
+
+ char **addgroups;
+ char **rmgroups;
+};
+
+/* default values for add operations */
+int useradd_defaults(TALLOC_CTX *mem_ctx,
+ struct confdb_ctx *confdb,
+ struct ops_ctx *data,
+ const char *gecos,
+ const char *homedir,
+ 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,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data);
+int userdel(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data);
+int usermod(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data);
+
+int groupadd(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data);
+int groupdel(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data);
+int groupmod(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ struct sysdb_handle *handle,
+ struct ops_ctx *data);
+
+void start_transaction(struct tools_ctx *tctx);
+void end_transaction(struct tools_ctx *tctx);
+
+int sysdb_getpwnam_sync(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ const char *name,
+ struct sss_domain_info *domain,
+ struct ops_ctx **out);
+
+int sysdb_getgrnam_sync(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sysdb_ctx *sysdb,
+ const char *name,
+ struct sss_domain_info *domain,
+ struct ops_ctx **out);
+
+#endif /* __SSS_OPS_H__ */
+
diff --git a/src/tools/sss_useradd.c b/src/tools/sss_useradd.c
new file mode 100644
index 000000000..077ac99f7
--- /dev/null
+++ b/src/tools/sss_useradd.c
@@ -0,0 +1,349 @@
+/*
+ SSSD
+
+ sss_useradd
+
+ Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2009
+
+ 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/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <talloc.h>
+#include <popt.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "tools/tools_util.h"
+#include "tools/sss_sync_ops.h"
+
+static void get_gid_callback(void *ptr, int error, struct ldb_result *res)
+{
+ struct tools_ctx *tctx = talloc_get_type(ptr, struct tools_ctx);
+
+ if (error) {
+ tctx->error = error;
+ return;
+ }
+
+ switch (res->count) {
+ case 0:
+ tctx->error = ENOENT;
+ break;
+
+ case 1:
+ tctx->octx->gid = ldb_msg_find_attr_as_uint(res->msgs[0], SYSDB_GIDNUM, 0);
+ if (tctx->octx->gid == 0) {
+ tctx->error = ERANGE;
+ }
+ break;
+
+ default:
+ tctx->error = EFAULT;
+ break;
+ }
+}
+
+/* Returns a gid for a given groupname. If a numerical gid
+ * is given, returns that as integer (rationale: shadow-utils)
+ * On error, returns -EINVAL
+ */
+static int get_gid(struct tools_ctx *tctx, const char *groupname)
+{
+ char *end_ptr;
+ int ret;
+
+ errno = 0;
+ tctx->octx->gid = strtoul(groupname, &end_ptr, 10);
+ if (groupname == '\0' || *end_ptr != '\0' ||
+ errno != 0 || tctx->octx->gid == 0) {
+ /* Does not look like a gid - find the group name */
+
+ ret = sysdb_getgrnam(tctx->octx, tctx->sysdb,
+ tctx->octx->domain, groupname,
+ get_gid_callback, tctx);
+ if (ret != EOK) {
+ DEBUG(1, ("sysdb_getgrnam failed: %d\n", ret));
+ goto done;
+ }
+
+ tctx->error = EOK;
+ tctx->octx->gid = 0;
+ while ((tctx->error == EOK) && (tctx->octx->gid == 0)) {
+ tevent_loop_once(tctx->ev);
+ }
+
+ if (tctx->error) {
+ DEBUG(1, ("sysdb_getgrnam failed: %d\n", ret));
+ goto done;
+ }
+ }
+
+done:
+ return ret;
+}
+
+int main(int argc, const char **argv)
+{
+ uid_t pc_uid = 0;
+ const char *pc_group = NULL;
+ const char *pc_gecos = NULL;
+ 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 },
+ { "uid", 'u', POPT_ARG_INT, &pc_uid, 0, _("The UID of the user"), NULL },
+ { "gid", 'g', POPT_ARG_STRING, &pc_group, 0, _("The GID or group name of the user"), NULL },
+ { "gecos", 'c', POPT_ARG_STRING, &pc_gecos, 0, _("The comment string"), NULL },
+ { "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;
+ struct tools_ctx *tctx = NULL;
+ char *groups = NULL;
+ char *badgroup = NULL;
+ int ret;
+
+ debug_prg_name = argv[0];
+
+ ret = set_locale();
+ if (ret != EOK) {
+ DEBUG(1, ("set_locale failed (%d): %s\n", ret, strerror(ret)));
+ ERROR("Error setting the locale\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* parse parameters */
+ pc = poptGetContext(NULL, argc, argv, long_options, 0);
+ poptSetOtherOptionHelp(pc, "USERNAME");
+ while ((ret = poptGetNextOpt(pc)) > 0) {
+ 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;
+ }
+ }
+
+ debug_level = pc_debug;
+
+ if (ret != -1) {
+ usage(pc, poptStrerror(ret));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* username is an argument without --option */
+ pc_username = poptGetArg(pc);
+ if (pc_username == NULL) {
+ usage(pc, (_("Specify user to add\n")));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ CHECK_ROOT(ret, debug_prg_name);
+
+ ret = init_sss_tools(&tctx);
+ if (ret != EOK) {
+ DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
+ if (ret == ENOENT) {
+ ERROR("Error initializing the tools - no local domain\n");
+ } else {
+ ERROR("Error initializing the tools\n");
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* if the domain was not given as part of FQDN, default to local domain */
+ ret = parse_name_domain(tctx, pc_username);
+ if (ret != EOK) {
+ ERROR("Invalid domain specified in FQDN\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ if (groups) {
+ ret = parse_groups(tctx, groups, &tctx->octx->addgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse groups to add the user to\n"));
+ ERROR("Internal error while parsing parameters\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = parse_group_name_domain(tctx, tctx->octx->addgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse FQDN groups to add the user to\n"));
+ ERROR("Groups must be in the same domain as user\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* Check group names in the LOCAL domain */
+ ret = check_group_names(tctx, tctx->octx->addgroups, &badgroup);
+ if (ret != EOK) {
+ ERROR("Cannot find group %s in local domain\n", badgroup);
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ }
+
+ /* Same as shadow-utils useradd, -g can specify gid or group name */
+ if (pc_group != NULL) {
+ ret = get_gid(tctx, pc_group);
+ if (ret != EOK) {
+ ERROR("Cannot get group information for the user\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ }
+
+ tctx->octx->uid = pc_uid;
+
+ /*
+ * 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_create_home, pc_skeldir);
+ if (ret != EOK) {
+ ERROR("Cannot set default values\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* arguments processed, go on to actual work */
+ if (id_in_range(tctx->octx->uid, tctx->octx->domain) != EOK) {
+ ERROR("The selected UID is outside the allowed range\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ start_transaction(tctx);
+ if (tctx->error != EOK) {
+ goto done;
+ }
+
+ /* useradd */
+ ret = useradd(tctx, tctx->ev, tctx->sysdb, tctx->handle, tctx->octx);
+ if (ret != EOK) {
+ tctx->error = ret;
+
+ /* cancel transaction */
+ talloc_zfree(tctx->handle);
+ goto done;
+ }
+
+ 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) {
+ case ERANGE:
+ ERROR("Could not allocate ID for the user - domain full?\n");
+ break;
+
+ case EEXIST:
+ ERROR("A user or group with the same name or ID already exists\n");
+ break;
+
+ default:
+ DEBUG(1, ("sysdb operation failed (%d)[%s]\n",
+ tctx->error, strerror(tctx->error)));
+ ERROR("Transaction error. Could not add user.\n");
+ break;
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = EXIT_SUCCESS;
+
+fini:
+ poptFreeContext(pc);
+ talloc_free(tctx);
+ free(groups);
+ exit(ret);
+}
diff --git a/src/tools/sss_userdel.c b/src/tools/sss_userdel.c
new file mode 100644
index 000000000..e84d78b17
--- /dev/null
+++ b/src/tools/sss_userdel.c
@@ -0,0 +1,205 @@
+/*
+ SSSD
+
+ sss_userdel
+
+ Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009
+
+ 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/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <talloc.h>
+#include <popt.h>
+
+#include "db/sysdb.h"
+#include "util/util.h"
+#include "tools/tools_util.h"
+#include "tools/sss_sync_ops.h"
+
+int main(int argc, const char **argv)
+{
+ int ret = EXIT_SUCCESS;
+ struct tools_ctx *tctx = NULL;
+ 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
+ };
+
+ debug_prg_name = argv[0];
+
+ ret = set_locale();
+ if (ret != EOK) {
+ DEBUG(1, ("set_locale failed (%d): %s\n", ret, strerror(ret)));
+ ERROR("Error setting the locale\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* parse parameters */
+ pc = poptGetContext(NULL, argc, argv, long_options, 0);
+ poptSetOtherOptionHelp(pc, "USERNAME");
+ 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;
+ }
+
+ pc_username = poptGetArg(pc);
+ if (pc_username == NULL) {
+ usage(pc, _("Specify user to delete\n"));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ CHECK_ROOT(ret, debug_prg_name);
+
+ ret = init_sss_tools(&tctx);
+ if (ret != EOK) {
+ DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
+ if (ret == ENOENT) {
+ ERROR("Error initializing the tools - no local domain\n");
+ } else {
+ ERROR("Error initializing the tools\n");
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* if the domain was not given as part of FQDN, default to local domain */
+ ret = parse_name_domain(tctx, pc_username);
+ if (ret != EOK) {
+ ERROR("Invalid domain specified in FQDN\n");
+ ret = EXIT_FAILURE;
+ 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;
+ }
+
+ 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;
+ }
+
+ if ((tctx->octx->uid < tctx->local->id_min) ||
+ (tctx->local->id_max && tctx->octx->uid > tctx->local->id_max)) {
+ ERROR("User %s is outside the defined ID range for domain\n",
+ tctx->octx->name);
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ start_transaction(tctx);
+ if (tctx->error != EOK) {
+ goto done;
+ }
+
+ /* userdel */
+ ret = userdel(tctx, tctx->ev, tctx->sysdb, tctx->handle, tctx->octx);
+ if (ret != EOK) {
+ tctx->error = ret;
+
+ /* cancel transaction */
+ talloc_zfree(tctx->handle);
+ goto done;
+ }
+
+ 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 (ret) {
+ DEBUG(1, ("sysdb operation failed (%d)[%s]\n", ret, strerror(ret)));
+ switch (ret) {
+ case ENOENT:
+ ERROR("No such user in local domain. "
+ "Removing users only allowed in local domain.\n");
+ break;
+
+ default:
+ ERROR("Internal error. Could not remove user.\n");
+ break;
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = EXIT_SUCCESS;
+
+fini:
+ talloc_free(tctx);
+ poptFreeContext(pc);
+ exit(ret);
+}
+
diff --git a/src/tools/sss_usermod.c b/src/tools/sss_usermod.c
new file mode 100644
index 000000000..a272bc55e
--- /dev/null
+++ b/src/tools/sss_usermod.c
@@ -0,0 +1,265 @@
+/*
+ SSSD
+
+ sss_usermod
+
+ Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009
+
+ 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/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <talloc.h>
+#include <popt.h>
+#include <errno.h>
+#include <pwd.h>
+#include <unistd.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "tools/tools_util.h"
+#include "tools/sss_sync_ops.h"
+
+int main(int argc, const char **argv)
+{
+ int pc_lock = 0;
+ uid_t pc_uid = 0;
+ gid_t pc_gid = 0;
+ char *pc_gecos = NULL;
+ char *pc_home = NULL;
+ char *pc_shell = NULL;
+ int pc_debug = 0;
+ 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 },
+ { "uid", 'u', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_uid, 0, _("The UID of the user"), NULL },
+ { "gid", 'g', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_gid, 0, _("The GID of the user"), NULL },
+ { "gecos", 'c', POPT_ARG_STRING, &pc_gecos, 0, _("The comment string"), NULL },
+ { "home", 'h', POPT_ARG_STRING, &pc_home, 0, _("Home directory"), NULL },
+ { "shell", 's', POPT_ARG_STRING, &pc_shell, 0, _("Login shell"), NULL },
+ { "append-group", 'a', POPT_ARG_STRING, NULL, 'a', _("Groups to add this user to"), NULL },
+ { "remove-group", 'r', POPT_ARG_STRING, NULL, 'r', _("Groups to remove this user from"), NULL },
+ { "lock", 'L', POPT_ARG_NONE, NULL, 'L', _("Lock the account"), NULL },
+ { "unlock", 'U', POPT_ARG_NONE, NULL, 'U', _("Unlock the account"), NULL },
+ POPT_TABLEEND
+ };
+ poptContext pc = NULL;
+ char *addgroups = NULL, *rmgroups = NULL;
+ int ret;
+ const char *pc_username = NULL;
+ struct tools_ctx *tctx = NULL;
+ char *badgroup = NULL;
+
+ debug_prg_name = argv[0];
+
+ ret = set_locale();
+ if (ret != EOK) {
+ DEBUG(1, ("set_locale failed (%d): %s\n", ret, strerror(ret)));
+ ERROR("Error setting the locale\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* parse parameters */
+ pc = poptGetContext(NULL, argc, argv, long_options, 0);
+ poptSetOtherOptionHelp(pc, "USERNAME");
+ while ((ret = poptGetNextOpt(pc)) > 0) {
+ switch (ret) {
+ case 'a':
+ addgroups = poptGetOptArg(pc);
+ if (addgroups == NULL) {
+ ret = -1;
+ }
+ break;
+
+ case 'r':
+ rmgroups = poptGetOptArg(pc);
+ if (rmgroups == NULL) {
+ ret = -1;
+ }
+ break;
+
+ case 'L':
+ pc_lock = DO_LOCK;
+ break;
+
+ case 'U':
+ pc_lock = DO_UNLOCK;
+ break;
+ }
+ }
+
+ if (ret != -1) {
+ usage(pc, poptStrerror(ret));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ debug_level = pc_debug;
+
+ /* username is an argument without --option */
+ pc_username = poptGetArg(pc);
+ if (pc_username == NULL) {
+ usage(pc, _("Specify user to modify\n"));
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ CHECK_ROOT(ret, debug_prg_name);
+
+ ret = init_sss_tools(&tctx);
+ if (ret != EOK) {
+ DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
+ if (ret == ENOENT) {
+ ERROR("Error initializing the tools - no local domain\n");
+ } else {
+ ERROR("Error initializing the tools\n");
+ }
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* if the domain was not given as part of FQDN, default to local domain */
+ ret = parse_name_domain(tctx, pc_username);
+ if (ret != EOK) {
+ ERROR("Invalid domain specified in FQDN\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ /* check the username to be able to give sensible error message */
+ ret = sysdb_getpwnam_sync(tctx, tctx->ev, tctx->sysdb,
+ tctx->octx->name, tctx->local,
+ &tctx->octx);
+ if (ret != EOK) {
+ ERROR("Cannot find user in local domain, "
+ "modifying users is allowed only in local domain\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ if (id_in_range(tctx->octx->uid, tctx->octx->domain) != EOK) {
+ ERROR("The selected UID is outside the allowed range\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ if (addgroups) {
+ ret = parse_groups(tctx, addgroups, &tctx->octx->addgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse groups to add the user to\n"));
+ ERROR("Internal error while parsing parameters\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = parse_group_name_domain(tctx, tctx->octx->addgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse FQDN groups to add the user to\n"));
+ ERROR("Groups must be in the same domain as user\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* Check group names in the LOCAL domain */
+ ret = check_group_names(tctx, tctx->octx->addgroups, &badgroup);
+ if (ret != EOK) {
+ ERROR("Cannot find group %s in local domain, "
+ "only groups in local domain are allowed\n", badgroup);
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ }
+
+ if (rmgroups) {
+ ret = parse_groups(tctx, rmgroups, &tctx->octx->rmgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse groups to remove the user from\n"));
+ ERROR("Internal error while parsing parameters\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = parse_group_name_domain(tctx, tctx->octx->rmgroups);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse FQDN groups to remove the user from\n"));
+ ERROR("Groups must be in the same domain as user\n");
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ /* Check group names in the LOCAL domain */
+ ret = check_group_names(tctx, tctx->octx->rmgroups, &badgroup);
+ if (ret != EOK) {
+ ERROR("Cannot find group %s in local domain, "
+ "only groups in local domain are allowed\n", badgroup);
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+ }
+
+ tctx->octx->gecos = pc_gecos;
+ tctx->octx->home = pc_home;
+ tctx->octx->shell = pc_shell;
+ tctx->octx->uid = pc_uid;
+ tctx->octx->gid = pc_gid;
+ tctx->octx->lock = pc_lock;
+
+ start_transaction(tctx);
+ if (tctx->error != EOK) {
+ goto done;
+ }
+
+ /* usermod */
+ ret = usermod(tctx, tctx->ev, tctx->sysdb, tctx->handle, tctx->octx);
+ if (ret != EOK) {
+ tctx->error = ret;
+
+ /* cancel transaction */
+ talloc_zfree(tctx->handle);
+ goto done;
+ }
+
+ end_transaction(tctx);
+
+done:
+ if (tctx->error) {
+ ret = tctx->error;
+ switch (ret) {
+ case ENOENT:
+ ERROR("Could not modify user - check if group names are correct\n");
+ break;
+
+ case EFAULT:
+ ERROR("Could not modify user - user already member of groups?\n");
+ break;
+
+ default:
+ ERROR("Transaction error. Could not modify user.\n");
+ break;
+ }
+
+ ret = EXIT_FAILURE;
+ goto fini;
+ }
+
+ ret = EXIT_SUCCESS;
+
+fini:
+ free(addgroups);
+ free(rmgroups);
+ poptFreeContext(pc);
+ talloc_free(tctx);
+ exit(ret);
+}
diff --git a/src/tools/tools_util.c b/src/tools/tools_util.c
new file mode 100644
index 000000000..97945238e
--- /dev/null
+++ b/src/tools/tools_util.c
@@ -0,0 +1,520 @@
+/*
+ SSSD
+
+ tools_utils.c
+
+ Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009
+
+ 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/>.
+*/
+
+#include <talloc.h>
+#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"
+#include "db/sysdb.h"
+#include "tools/tools_util.h"
+#include "tools/sss_sync_ops.h"
+
+static int setup_db(struct tools_ctx *ctx)
+{
+ char *confdb_path;
+ int ret;
+
+ /* Create the event context */
+ ctx->ev = tevent_context_init(ctx);
+ if (ctx->ev == NULL) {
+ DEBUG(1, ("Could not create event context\n"));
+ return EIO;
+ }
+
+ confdb_path = talloc_asprintf(ctx, "%s/%s", DB_PATH, CONFDB_FILE);
+ if (confdb_path == NULL) {
+ return ENOMEM;
+ }
+
+ /* Connect to the conf db */
+ ret = confdb_init(ctx, &ctx->confdb, confdb_path);
+ if (ret != EOK) {
+ DEBUG(1, ("Could not initialize connection to the confdb\n"));
+ return ret;
+ }
+
+ ret = confdb_get_domain(ctx->confdb, "local", &ctx->local);
+ if (ret != EOK) {
+ DEBUG(1, ("Could not get 'local' domain: [%d] [%s]\n", ret, strerror(ret)));
+ return ret;
+ }
+
+ /* open 'local' sysdb at default path */
+ ret = sysdb_domain_init(ctx, ctx->ev, ctx->local, DB_PATH, &ctx->sysdb);
+ if (ret != EOK) {
+ DEBUG(1, ("Could not initialize connection to the sysdb\n"));
+ return ret;
+ }
+
+ talloc_free(confdb_path);
+ return EOK;
+}
+
+/*
+ * Print poptUsage as well as our error message
+ */
+void usage(poptContext pc, const char *error)
+{
+ poptPrintUsage(pc, stderr, 0);
+ if (error) fprintf(stderr, "%s", error);
+}
+
+int parse_groups(TALLOC_CTX *mem_ctx, const char *optstr, char ***_out)
+{
+ char **out;
+ char *orig, *n, *o;
+ char delim = ',';
+ unsigned int tokens = 1;
+ int i;
+
+ orig = talloc_strdup(mem_ctx, optstr);
+ if (!orig) return ENOMEM;
+
+ n = orig;
+ tokens = 1;
+ while ((n = strchr(n, delim))) {
+ n++;
+ tokens++;
+ }
+
+ out = talloc_array(mem_ctx, char *, tokens+1);
+ if (!out) {
+ talloc_free(orig);
+ return ENOMEM;
+ }
+
+ n = o = orig;
+ for (i = 0; i < tokens; i++) {
+ o = n;
+ n = strchr(n, delim);
+ if (!n) {
+ break;
+ }
+ *n = '\0';
+ n++;
+ out[i] = talloc_strdup(out, o);
+ }
+ out[tokens-1] = talloc_strdup(out, o);
+ out[tokens] = NULL;
+
+ talloc_free(orig);
+ *_out = out;
+ return EOK;
+}
+
+int parse_group_name_domain(struct tools_ctx *tctx,
+ char **groups)
+{
+ int i;
+ int ret;
+ char *name = NULL;
+ char *domain = NULL;
+
+ if (!groups) {
+ return EOK;
+ }
+
+ for (i = 0; groups[i]; ++i) {
+ ret = sss_parse_name(tctx, tctx->snctx, groups[i], &domain, &name);
+
+ /* If FQDN is specified, it must be within the same domain as user */
+ if (domain) {
+ if (strcmp(domain, tctx->octx->domain->name) != 0) {
+ return EINVAL;
+ }
+
+ /* Use only groupname */
+ talloc_zfree(groups[i]);
+ groups[i] = talloc_strdup(tctx, name);
+ if (groups[i] == NULL) {
+ return ENOMEM;
+ }
+ }
+
+ talloc_zfree(name);
+ talloc_zfree(domain);
+ }
+
+ talloc_zfree(name);
+ talloc_zfree(domain);
+ return EOK;
+}
+
+int parse_name_domain(struct tools_ctx *tctx,
+ const char *fullname)
+{
+ int ret;
+ char *domain = NULL;
+
+ ret = sss_parse_name(tctx, tctx->snctx, fullname, &domain, &tctx->octx->name);
+ if (ret != EOK) {
+ DEBUG(0, ("Cannot parse full name\n"));
+ return ret;
+ }
+ DEBUG(5, ("Parsed username: %s\n", tctx->octx->name));
+
+ if (domain) {
+ DEBUG(5, ("Parsed domain: %s\n", domain));
+ /* only the local domain, whatever named is allowed in tools */
+ if (strcasecmp(domain, tctx->local->name) != 0) {
+ DEBUG(1, ("Invalid domain %s specified in FQDN\n", domain));
+ return EINVAL;
+ }
+ }
+
+ return EOK;
+}
+
+int check_group_names(struct tools_ctx *tctx,
+ char **grouplist,
+ char **badgroup)
+{
+ int ret;
+ int i;
+ struct ops_ctx *groupinfo;
+
+ groupinfo = talloc_zero(tctx, struct ops_ctx);
+ if (!groupinfo) {
+ return ENOMEM;
+ }
+
+ ret = EOK;
+ for (i=0; grouplist[i]; ++i) {
+ ret = sysdb_getgrnam_sync(tctx,
+ tctx->ev,
+ tctx->sysdb,
+ grouplist[i],
+ tctx->local,
+ &groupinfo);
+ if (ret) {
+ DEBUG(6, ("Cannot find group %s, ret: %d\n", grouplist[i], ret));
+ break;
+ }
+ }
+
+ talloc_zfree(groupinfo);
+ *badgroup = grouplist[i];
+ return ret;
+}
+
+int id_in_range(uint32_t id,
+ struct sss_domain_info *dom)
+{
+ if (id &&
+ ((id < dom->id_min) ||
+ (dom->id_max && id > dom->id_max))) {
+ return ERANGE;
+ }
+
+ return EOK;
+}
+
+int set_locale(void)
+{
+ char *c;
+
+ c = setlocale(LC_ALL, "");
+ if (c == NULL) {
+ return EIO;
+ }
+
+ errno = 0;
+ c = bindtextdomain(PACKAGE, LOCALEDIR);
+ if (c == NULL) {
+ return errno;
+ }
+
+ errno = 0;
+ c = textdomain(PACKAGE);
+ if (c == NULL) {
+ return errno;
+ }
+
+ return EOK;
+}
+
+int init_sss_tools(struct tools_ctx **_tctx)
+{
+ int ret;
+ struct tools_ctx *tctx;
+
+ tctx = talloc_zero(NULL, struct tools_ctx);
+ if (tctx == NULL) {
+ DEBUG(1, ("Could not allocate memory for tools context\n"));
+ return ENOMEM;
+ }
+
+ /* Connect to the database */
+ ret = setup_db(tctx);
+ if (ret != EOK) {
+ DEBUG(1, ("Could not set up database\n"));
+ goto fini;
+ }
+
+ ret = sss_names_init(tctx, tctx->confdb, &tctx->snctx);
+ if (ret != EOK) {
+ DEBUG(1, ("Could not set up parsing\n"));
+ goto fini;
+ }
+
+ tctx->octx = talloc_zero(tctx, struct ops_ctx);
+ if (!tctx->octx) {
+ DEBUG(1, ("Could not allocate memory for data context\n"));
+ ERROR("Out of memory\n");
+ ret = ENOMEM;
+ goto fini;
+ }
+ tctx->octx->domain = tctx->local;
+
+ *_tctx = tctx;
+ ret = EOK;
+
+fini:
+ if (ret != EOK) talloc_free(tctx);
+ 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/src/tools/tools_util.h b/src/tools/tools_util.h
new file mode 100644
index 000000000..a643e739f
--- /dev/null
+++ b/src/tools/tools_util.h
@@ -0,0 +1,108 @@
+/*
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+ Simo Sorce <ssorce@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/>.
+*/
+
+
+#ifndef __TOOLS_UTIL_H__
+#define __TOOLS_UTIL_H__
+
+#include <popt.h>
+
+#include "util/util.h"
+
+#define CHECK_ROOT(val, prg_name) do { \
+ val = getuid(); \
+ if (val != 0) { \
+ DEBUG(1, ("Running under %d, must be root\n", val)); \
+ ERROR("%s must be run as root\n", prg_name); \
+ val = EXIT_FAILURE; \
+ goto fini; \
+ } \
+} while(0)
+
+struct tools_ctx {
+ struct tevent_context *ev;
+ struct confdb_ctx *confdb;
+ struct sysdb_ctx *sysdb;
+
+ struct sss_names_ctx *snctx;
+ struct sss_domain_info *local;
+
+ struct ops_ctx *octx;
+
+ struct sysdb_handle *handle;
+ bool transaction_done;
+ int error;
+};
+
+int init_sss_tools(struct tools_ctx **_tctx);
+
+void usage(poptContext pc, const char *error);
+
+int set_locale(void);
+
+
+int parse_name_domain(struct tools_ctx *tctx,
+ const char *fullname);
+
+int id_in_range(uint32_t id,
+ struct sss_domain_info *dom);
+
+int parse_groups(TALLOC_CTX *mem_ctx,
+ const char *optstr,
+ char ***_out);
+
+int parse_group_name_domain(struct tools_ctx *tctx,
+ char **groups);
+
+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);
+
+/* from files.c */
+int remove_tree(const char *root);
+
+int copy_tree(const char *src_root,
+ const char *dst_root,
+ uid_t uid, gid_t gid);
+
+int selinux_file_context(const char *dst_name);
+int reset_selinux_file_context(void);
+
+#endif /* __TOOLS_UTIL_H__ */