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