>From a706c3890b0c56d9ce03038a46def5221886b0b0 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Fri, 14 Aug 2009 15:09:57 +0200
Subject: [PATCH 2/3] Decouple synchronous sysdb interface from tools

Instead of working directly with async code in tools, create synchronous
wrappers that could be used by tools and python bindings.

Also resolves many issues with code duplication in tools and thus fixes
ticket #87
---
 server/Makefile.am                 |    6 +-
 server/tools/common/sss_sync_ops.c |  703 ++++++++++++++++++++++++++++++++++++
 server/tools/common/sss_sync_ops.h |   89 +++++
 server/tools/common/tools_util.c   |  370 +++++++++++++++++++
 server/tools/common/tools_util.h   |   68 ++++
 server/tools/sss_groupadd.c        |   83 +----
 server/tools/sss_groupdel.c        |   89 +-----
 server/tools/sss_groupmod.c        |  231 +------------
 server/tools/sss_useradd.c         |  200 +----------
 server/tools/sss_userdel.c         |   92 +-----
 server/tools/sss_usermod.c         |  297 +---------------
 server/tools/tools_util.c          |  370 -------------------
 server/tools/tools_util.h          |   99 -----
 13 files changed, 1260 insertions(+), 1437 deletions(-)
 create mode 100644 server/tools/common/sss_sync_ops.c
 create mode 100644 server/tools/common/sss_sync_ops.h
 create mode 100644 server/tools/common/tools_util.c
 create mode 100644 server/tools/common/tools_util.h
 delete mode 100644 server/tools/tools_util.c
 delete mode 100644 server/tools/tools_util.h

diff --git a/server/Makefile.am b/server/Makefile.am
index 12ec4b1..0a81ab4 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -169,7 +169,8 @@ SSSD_RESPONDER_OBJ = \
     responder/common/responder_packet.c
 
 SSSD_TOOLS_OBJ = \
-    tools/tools_util.c
+    tools/common/sss_sync_ops.c \
+    tools/common/tools_util.c
 
 SSSD_RESOLV_OBJ = \
     resolv/async_resolv.c
@@ -226,7 +227,8 @@ dist_noinst_HEADERS = \
     providers/krb5/krb5_auth.h \
     providers/ldap/sdap.h \
     providers/ldap/sdap_async.h \
-    tools/tools_util.h \
+    tools/common/tools_util.h \
+    tools/common/sss_sync_ops.h \
     krb5_plugin/sssd_krb5_locator_plugin.h \
     resolv/async_resolv.h \
     resolv/ares/ares_parse_srv_reply.h \
diff --git a/server/tools/common/sss_sync_ops.c b/server/tools/common/sss_sync_ops.c
new file mode 100644
index 0000000..70fa2cd
--- /dev/null
+++ b/server/tools/common/sss_sync_ops.c
@@ -0,0 +1,703 @@
+/*
+ * Authors:
+ *   Jakub Hrozek <jhrozek@redhat.com>
+ *
+ * Copyright (C) 2008  Red Hat
+ * see file 'COPYING' for use and warranty information
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 only
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <tevent.h>
+#include <talloc.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+
+#include "tools/common/sss_sync_ops.h"
+
+/* Default settings for user attributes */
+#define CONFDB_DFL_SECTION "config/user_defaults"
+
+#define DFL_SHELL_ATTR     "defaultShell"
+#define DFL_BASEDIR_ATTR   "baseDirectory"
+
+#define DFL_SHELL_VAL      "/bin/bash"
+#define DFL_BASEDIR_VAL    "/home"
+
+#define VAR_CHECK(var, val, attr, msg) do { \
+        if (var != (val)) { \
+            DEBUG(1, (msg" attribute: %s", attr)); \
+            return val; \
+        } \
+} while(0)
+
+/*
+ * common functions
+ */
+static int synchronous_transaction(TALLOC_CTX *mem_ctx,
+                                   void (cbc)(struct tevent_req *req),
+                                   struct ops_ctx *data)
+{
+    int ret;
+    struct tevent_req *req;
+
+    req = sysdb_transaction_send(mem_ctx, data->ctx->ev, data->ctx->sysdb);
+    if (!req) {
+        DEBUG(1, ("Could not start transaction"));
+        ret = ENOMEM;
+        goto done;
+    }
+    tevent_req_set_callback(req, cbc, data);
+
+    while (!data->done) {
+        tevent_loop_once(data->ctx->ev);
+    }
+
+done:
+    talloc_free(req);
+    return data->error;
+}
+
+static void req_done(struct tevent_req *req)
+{
+    struct ops_ctx *data = tevent_req_callback_data(req, struct ops_ctx);
+
+    data->error = sysdb_transaction_commit_recv(req);
+    data->done = true;
+}
+
+static void transaction_done(struct ops_ctx *data, int error)
+{
+    struct tevent_req *req;
+
+    if (error != EOK) {
+        goto fail;
+    }
+
+    req = sysdb_transaction_commit_send(data, data->ev, data->handle);
+    if (!req) {
+        error = ENOMEM;
+        goto fail;
+    }
+    tevent_req_set_callback(req, req_done, data);
+
+    return;
+
+fail:
+    /* free transaction */
+    talloc_zfree(data->handle);
+
+    data->error = error;
+    data->done = true;
+}
+
+/*
+ * Generic add member to group
+ */
+static void add_to_groups_done(struct tevent_req *subreq);
+
+static void add_to_groups(struct ops_ctx *data)
+{
+    struct ldb_dn *parent_dn;
+    struct ldb_dn *member_dn;
+    struct tevent_req *subreq;
+
+    member_dn = data->member_dn_fn(data->ctx->sysdb, data,
+                                   data->domain->name, data->name);
+    if (!member_dn) {
+        return transaction_done(data, ENOMEM);
+    }
+
+    parent_dn = sysdb_group_dn(data->ctx->sysdb, data,
+                               data->domain->name,
+                               data->addgroups[data->cur]);
+    if (!parent_dn) {
+        return transaction_done(data, ENOMEM);
+    }
+
+    subreq = sysdb_mod_group_member_send(data,
+                                         data->ev,
+                                         data->handle,
+                                         member_dn,
+                                         parent_dn,
+                                         LDB_FLAG_MOD_ADD);
+    if (!subreq) {
+        return transaction_done(data, ENOMEM);
+    }
+    tevent_req_set_callback(subreq, add_to_groups_done, data);
+}
+
+static void add_to_groups_done(struct tevent_req *subreq)
+{
+    struct ops_ctx *data = tevent_req_callback_data(subreq,
+                                                    struct ops_ctx);
+    int ret;
+
+    ret = sysdb_mod_group_member_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret) {
+        return transaction_done(data, ret);
+    }
+
+    /* go on to next group */
+    data->cur++;
+
+    /* check if we added all of them */
+    if (data->addgroups[data->cur] == NULL) {
+        return transaction_done(data, EOK);
+    }
+
+    return add_to_groups(data);
+}
+
+/*
+ * Generic remove member from group
+ */
+static void remove_from_groups_done(struct tevent_req *req);
+
+static void remove_from_groups(struct ops_ctx *data)
+{
+    struct ldb_dn *parent_dn;
+    struct ldb_dn *member_dn;
+    struct tevent_req *req;
+
+    member_dn = data->member_dn_fn(data->ctx->sysdb, data,
+                                   data->domain->name, data->name);
+    if (!member_dn) {
+        return transaction_done(data, ENOMEM);
+    }
+
+    parent_dn = sysdb_group_dn(data->ctx->sysdb, data,
+                               data->domain->name,
+                               data->rmgroups[data->cur]);
+    if (!parent_dn) {
+        return transaction_done(data, ENOMEM);
+    }
+
+    req = sysdb_mod_group_member_send(data,
+                                      data->ev,
+                                      data->handle,
+                                      member_dn,
+                                      parent_dn,
+                                      LDB_FLAG_MOD_DELETE);
+    if (!req) {
+        return transaction_done(data, ENOMEM);
+    }
+    tevent_req_set_callback(req, remove_from_groups_done, data);
+}
+
+static void remove_from_groups_done(struct tevent_req *req)
+{
+    struct ops_ctx *data = tevent_req_callback_data(req, struct ops_ctx);
+    int ret;
+
+    ret = sysdb_mod_group_member_recv(req);
+    if (ret) {
+        return transaction_done(data, ret);
+    }
+    talloc_zfree(req);
+
+    /* go on to next group */
+    data->cur++;
+
+    /* check if we removed all of them */
+    if (data->rmgroups[data->cur] == NULL) {
+        data->cur = 0;
+        if (data->addgroups != NULL) {
+            return add_to_groups(data);
+        }
+        return transaction_done(data, EOK);
+    }
+
+    return remove_from_groups(data);
+}
+
+/*
+ * Add a user
+ */
+static void add_user_done(struct tevent_req *subreq);
+
+void add_user(struct tevent_req *req)
+{
+    struct ops_ctx *data = tevent_req_callback_data(req, struct ops_ctx);
+    struct tevent_req *subreq;
+    int ret;
+
+    data->member_dn_fn = sysdb_user_dn;
+
+    ret = sysdb_transaction_recv(req, data, &data->handle);
+    if (ret != EOK) {
+        return transaction_done(data, ret);
+    }
+
+    subreq = sysdb_add_user_send(data, data->ev, data->handle,
+                                 data->domain, data->name,
+                                 data->uid, data->gid,
+                                 data->gecos, data->home,
+                                 data->shell, NULL);
+    if (!subreq) {
+        transaction_done(data, ENOMEM);
+    }
+    tevent_req_set_callback(subreq, add_user_done, data);
+}
+
+static void add_user_done(struct tevent_req *subreq)
+{
+    struct ops_ctx *data = tevent_req_callback_data(subreq, struct ops_ctx);
+    int ret;
+
+    ret = sysdb_add_user_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret) {
+        return transaction_done(data, ret);
+    }
+
+    if (data->addgroups) {
+        return add_to_groups(data);
+    }
+
+    return transaction_done(data, ret);
+}
+
+/*
+ * Remove a user
+ */
+static void user_del_done(struct tevent_req *subreq);
+
+static void user_del(struct tevent_req *req)
+{
+    struct ops_ctx *data;
+    struct tevent_req *subreq;
+    struct ldb_dn *user_dn;
+    int ret;
+
+    data = tevent_req_callback_data(req, struct ops_ctx);
+
+    ret = sysdb_transaction_recv(req, data, &data->handle);
+    if (ret != EOK) {
+        return transaction_done(data, ret);
+    }
+
+    user_dn = sysdb_user_dn(data->ctx->sysdb, data,
+                            data->domain->name, data->name);
+    if (!user_dn) {
+        DEBUG(1, ("Could not construct a user DN\n"));
+        return transaction_done(data, ENOMEM);
+    }
+
+    subreq = sysdb_delete_entry_send(data, data->ev, data->handle, user_dn, false);
+    if (!subreq)
+        return transaction_done(data, ENOMEM);
+
+    tevent_req_set_callback(subreq, user_del_done, data);
+}
+
+static void user_del_done(struct tevent_req *subreq)
+{
+    struct ops_ctx *data = tevent_req_callback_data(subreq, struct ops_ctx);
+    int ret;
+
+    ret = sysdb_delete_entry_recv(subreq);
+
+    return transaction_done(data, ret);
+}
+
+/*
+ * Modify a user
+ */
+static void mod_user_attr_done(struct tevent_req *req);
+static void mod_user_cont(struct ops_ctx *data);
+
+static void mod_user(struct tevent_req *req)
+{
+    struct ops_ctx *data;
+    struct tevent_req *subreq;
+    int ret;
+
+    data = tevent_req_callback_data(req, struct ops_ctx);
+    data->member_dn_fn = sysdb_user_dn;
+
+    ret = sysdb_transaction_recv(req, data, &data->handle);
+    if (ret != EOK) {
+        return transaction_done(data, ret);
+    }
+
+    if (data->attrs->num != 0) {
+        subreq = sysdb_set_user_attr_send(data, data->ev, data->handle,
+                                          data->domain, data->name,
+                                          data->attrs, SYSDB_MOD_REP);
+        if (!subreq) {
+            return transaction_done(data, ENOMEM);
+        }
+        tevent_req_set_callback(subreq, mod_user_attr_done, data);
+        return;
+    }
+
+    return mod_user_cont(data);
+}
+
+static void mod_user_attr_done(struct tevent_req *subreq)
+{
+    struct ops_ctx *data = tevent_req_callback_data(subreq, struct ops_ctx);
+    int ret;
+
+    ret = sysdb_set_user_attr_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        return transaction_done(data, ret);
+    }
+
+    mod_user_cont(data);
+}
+
+static void mod_user_cont(struct ops_ctx *data)
+{
+    if (data->rmgroups != NULL) {
+        return remove_from_groups(data);
+    }
+
+    if (data->addgroups != NULL) {
+        return add_to_groups(data);
+    }
+
+    return transaction_done(data, EOK);
+}
+
+/*
+ * Add a group
+ */
+static void add_group_done(struct tevent_req *subreq);
+
+static void add_group(struct tevent_req *req)
+{
+    struct ops_ctx *data = tevent_req_callback_data(req, struct ops_ctx);
+    struct tevent_req *subreq;
+    int ret;
+
+    ret = sysdb_transaction_recv(req, data, &data->handle);
+    if (ret != EOK) {
+        return transaction_done(data, ret);
+    }
+
+    subreq = sysdb_add_group_send(data, data->ev, data->handle,
+                                  data->domain, data->name,
+                                  data->gid, NULL);
+    if (!subreq) {
+        transaction_done(data, ENOMEM);
+    }
+    tevent_req_set_callback(subreq, add_group_done, data);
+}
+
+static void add_group_done(struct tevent_req *subreq)
+{
+    struct ops_ctx *data = tevent_req_callback_data(subreq, struct ops_ctx);
+    int ret;
+
+    ret = sysdb_add_group_recv(subreq);
+    talloc_zfree(subreq);
+
+    return transaction_done(data, ret);
+}
+
+/*
+ * Delete a group
+ */
+static void group_del_done(struct tevent_req *subreq);
+
+static void group_del(struct tevent_req *req)
+{
+    struct ops_ctx *data = tevent_req_callback_data(req, struct ops_ctx);
+    struct tevent_req *subreq;
+    struct ldb_dn *group_dn;
+    int ret;
+
+    ret = sysdb_transaction_recv(req, data, &data->handle);
+    if (ret != EOK) {
+        return transaction_done(data, ret);
+    }
+
+    group_dn = sysdb_group_dn(data->ctx->sysdb, data,
+                              data->domain->name, data->name);
+    if (group_dn == NULL) {
+        DEBUG(1, ("Could not construct a group DN\n"));
+        return transaction_done(data, ENOMEM);
+    }
+
+    subreq = sysdb_delete_entry_send(data, data->ev, data->handle, group_dn, false);
+    if (!subreq)
+        return transaction_done(data, ENOMEM);
+
+    tevent_req_set_callback(subreq, group_del_done, data);
+}
+
+static void group_del_done(struct tevent_req *subreq)
+{
+    struct ops_ctx *data = tevent_req_callback_data(subreq, struct ops_ctx);
+    int ret;
+
+    ret = sysdb_delete_entry_recv(subreq);
+
+    return transaction_done(data, ret);
+}
+
+/*
+ * Modify a group
+ */
+static void mod_group_attr_done(struct tevent_req *req);
+static void mod_group_cont(struct ops_ctx *data);
+
+static void mod_group(struct tevent_req *req)
+{
+    struct ops_ctx *data;
+    struct tevent_req *subreq;
+    struct sysdb_attrs *attrs;
+    int ret;
+
+    data = tevent_req_callback_data(req, struct ops_ctx);
+    data->member_dn_fn = sysdb_group_dn;
+
+    ret = sysdb_transaction_recv(req, data, &data->handle);
+    if (ret != EOK) {
+        return transaction_done(data, ret);
+    }
+
+    if (data->gid != 0) {
+        attrs = sysdb_new_attrs(NULL);
+        if (!attrs) {
+            transaction_done(data, ENOMEM);
+        }
+        ret = sysdb_attrs_add_uint32(attrs, SYSDB_GIDNUM, data->gid);
+        if (ret) {
+            transaction_done(data, ret);
+        }
+
+        subreq = sysdb_set_group_attr_send(data, data->ev, data->handle,
+                                           data->domain, data->name,
+                                           attrs, SYSDB_MOD_REP);
+        if (!subreq) {
+            return transaction_done(data, ENOMEM);
+        }
+        tevent_req_set_callback(subreq, mod_group_attr_done, data);
+        return;
+    }
+
+    return mod_group_cont(data);
+}
+
+static void mod_group_attr_done(struct tevent_req *subreq)
+{
+    struct ops_ctx *data = tevent_req_callback_data(subreq, struct ops_ctx);
+    int ret;
+
+    ret = sysdb_set_group_attr_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        return transaction_done(data, ret);
+    }
+
+    mod_group_cont(data);
+}
+
+static void mod_group_cont(struct ops_ctx *data)
+{
+    if (data->rmgroups != NULL) {
+        return remove_from_groups(data);
+    }
+
+    if (data->addgroups != NULL) {
+        return add_to_groups(data);
+    }
+
+    return transaction_done(data, EOK);
+}
+
+/*
+ * Default values for add operations
+ */
+int useradd_defaults(TALLOC_CTX *mem_ctx,
+                     struct ops_ctx *data,
+                     const char *gecos,
+                     const char *homedir,
+                     const char *shell)
+{
+    int ret;
+    char *basedir = NULL;
+    char *dfl_shell = NULL;
+
+    data->gecos = talloc_strdup(mem_ctx, gecos ? gecos : data->name);
+    if (!data->gecos) {
+        return ENOMEM;
+    }
+
+    if (homedir) {
+        data->home = talloc_strdup(data, homedir);
+    } else {
+        ret = confdb_get_string(data->ctx->confdb, mem_ctx,
+                                CONFDB_DFL_SECTION, DFL_BASEDIR_ATTR,
+                                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;
+        }
+    }
+    if (!data->home) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    if (!shell) {
+        ret = confdb_get_string(data->ctx->confdb, mem_ctx,
+                                CONFDB_DFL_SECTION, DFL_SHELL_ATTR,
+                                DFL_SHELL_VAL, &dfl_shell);
+        if (ret != EOK) {
+            goto done;
+        }
+        shell = dfl_shell;
+    }
+    data->shell = talloc_strdup(mem_ctx, shell);
+    if (!data->shell) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+done:
+    talloc_free(dfl_shell);
+    talloc_free(basedir);
+    return ret;
+}
+
+int usermod_to_attrs(TALLOC_CTX *mem_ctx,
+                     struct ops_ctx *data,
+                     const char *gecos,
+                     const char *home,
+                     const char *shell,
+                     uid_t uid,
+                     gid_t gid,
+                     int lock)
+{
+    int ret;
+
+    if(shell) {
+        ret = sysdb_attrs_add_string(data->attrs,
+                                     SYSDB_SHELL,
+                                     shell);
+        VAR_CHECK(ret, EOK, SYSDB_SHELL,
+                  "Could not add attribute to changeset\n");
+    }
+
+    if(home) {
+        ret = sysdb_attrs_add_string(data->attrs,
+                                     SYSDB_HOMEDIR,
+                                     home);
+        VAR_CHECK(ret, EOK, SYSDB_HOMEDIR,
+                  "Could not add attribute to changeset\n");
+    }
+
+    if(gecos) {
+        ret = sysdb_attrs_add_string(data->attrs,
+                                     SYSDB_GECOS,
+                                     gecos);
+        VAR_CHECK(ret, EOK, SYSDB_GECOS,
+                  "Could not add attribute to changeset\n");
+    }
+
+    if(uid) {
+        ret = sysdb_attrs_add_long(data->attrs,
+                                   SYSDB_UIDNUM,
+                                   uid);
+        VAR_CHECK(ret, EOK, SYSDB_UIDNUM,
+                  "Could not add attribute to changeset\n");
+    }
+
+    if(gid) {
+        ret = sysdb_attrs_add_long(data->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(data->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(data->attrs,
+                                     SYSDB_DISABLED,
+                                     "false");
+        VAR_CHECK(ret, EOK, SYSDB_DISABLED,
+                  "Could not add attribute to changeset\n");
+    }
+
+    return EOK;
+}
+
+/*
+ * Public interface for adding users
+ */
+int useradd(TALLOC_CTX *mem_ctx, struct ops_ctx *data)
+{
+    return synchronous_transaction(mem_ctx, add_user, data);
+}
+
+/*
+ * Public interface for deleting users
+ */
+int userdel(TALLOC_CTX *mem_ctx, struct ops_ctx *data)
+{
+    return synchronous_transaction(mem_ctx, user_del, data);
+}
+
+/*
+ * Public interface for modifying users
+ */
+int usermod(TALLOC_CTX *mem_ctx, struct ops_ctx *data)
+{
+    return synchronous_transaction(mem_ctx, mod_user, data);
+}
+
+/*
+ * Public interface for adding groups
+ */
+int groupadd(TALLOC_CTX *mem_ctx, struct ops_ctx *data)
+{
+    return synchronous_transaction(mem_ctx, add_group, data);
+}
+
+/*
+ * Public interface for deleting groups
+ */
+int groupdel(TALLOC_CTX *mem_ctx, struct ops_ctx *data)
+{
+    return synchronous_transaction(mem_ctx, group_del, data);
+}
+
+/*
+ * Public interface for modifying groups
+ */
+int groupmod(TALLOC_CTX *mem_ctx, struct ops_ctx *data)
+{
+    return synchronous_transaction(mem_ctx, mod_group, data);
+}
+
diff --git a/server/tools/common/sss_sync_ops.h b/server/tools/common/sss_sync_ops.h
new file mode 100644
index 0000000..771d5d8
--- /dev/null
+++ b/server/tools/common/sss_sync_ops.h
@@ -0,0 +1,89 @@
+/*
+ * Authors:
+ *   Jakub Hrozek <jhrozek@redhat.com>
+ *
+ * Copyright (C) 2009  Red Hat
+ * see file 'COPYING' for use and warranty information
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 only
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __SSS_OPS_H__
+#define __SSS_OPS_H__
+
+#include <stdbool.h>
+
+typedef struct ldb_dn* (*dn_fn)(struct sysdb_ctx *, void *, const char *, const char *);
+
+struct tools_ctx {
+    struct tevent_context *ev;
+    struct confdb_ctx *confdb;
+    struct sysdb_ctx *sysdb;
+    struct sss_names_ctx *snctx;
+
+    struct sss_domain_info *domains;
+};
+
+struct ops_ctx {
+    struct tools_ctx *ctx;
+    struct tevent_context *ev;
+    struct sss_domain_info *domain;
+
+    char *name;
+    uid_t uid;
+    gid_t gid;
+    char *gecos;
+    char *home;
+    char *shell;
+    struct sysdb_attrs *attrs;
+
+    char **addgroups;
+    char **rmgroups;
+    int cur;
+    dn_fn member_dn_fn;
+
+    struct sysdb_handle *handle;
+    int error;
+    bool done;
+};
+
+/* default values for add operations */
+int useradd_defaults(TALLOC_CTX *mem_ctx,
+                     struct ops_ctx *data,
+                     const char *gecos,
+                     const char *homedir,
+                     const char *shell);
+
+#define DO_LOCK     1
+#define DO_UNLOCK   2
+
+int usermod_to_attrs(TALLOC_CTX *mem_ctx,
+                     struct ops_ctx *data,
+                     const char *gecos,
+                     const char *home,
+                     const char *shell,
+                     uid_t uid,
+                     gid_t gid,
+                     int lock);
+
+/* synchronous operations */
+int useradd(TALLOC_CTX *mem_ctx, struct ops_ctx *data);
+int userdel(TALLOC_CTX *mem_ctx, struct ops_ctx *data);
+int usermod(TALLOC_CTX *mem_ctx, struct ops_ctx *data);
+
+int groupadd(TALLOC_CTX *mem_ctx, struct ops_ctx *data);
+int groupdel(TALLOC_CTX *mem_ctx, struct ops_ctx *data);
+int groupmod(TALLOC_CTX *mem_ctx, struct ops_ctx *data);
+#endif /* __SSS_OPS_H__ */
+
diff --git a/server/tools/common/tools_util.c b/server/tools/common/tools_util.c
new file mode 100644
index 0000000..0d8e1f4
--- /dev/null
+++ b/server/tools/common/tools_util.c
@@ -0,0 +1,370 @@
+/*
+   SSSD
+
+   tools_utils.c
+
+   Copyright (C) Jakub Hrozek <jhrozek@redhat.com>        2009
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <talloc.h>
+#include <tevent.h>
+#include <popt.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "util/util.h"
+#include "confdb/confdb.h"
+#include "db/sysdb.h"
+#include "tools/common/tools_util.h"
+
+/*
+ * Returns:
+ * 0 = yes, local domain proxying to files
+ * -1 = no, other type of domain
+ * > 0 = error code
+ */
+static int is_domain_local_legacy(struct tools_ctx *ctx, struct sss_domain_info *dom)
+{
+    char *libname = NULL;
+    char *conf_path = NULL;
+    int ret = -1;
+
+    /* Is there a better way to find out? Having LEGACYLOCAL as reserved would help */
+    conf_path = talloc_asprintf(ctx, "config/domains/%s", dom->name);
+    if (conf_path == NULL ) {
+        return ENOMEM;
+    }
+
+    ret = confdb_get_string(ctx->confdb, ctx, conf_path,
+                            "libName", NULL, &libname);
+    if (ret != EOK) {
+        talloc_free(conf_path);
+        return ret;
+    }
+    if (libname == NULL) {
+        talloc_free(conf_path);
+        return -1;
+    }
+
+    if (strcasecmp(libname, "files") == 0) {
+        talloc_free(conf_path);
+        talloc_free(libname);
+        return EOK;
+    }
+
+    talloc_free(conf_path);
+    talloc_free(libname);
+    return -1;
+}
+
+enum id_domain get_domain_type(struct tools_ctx *ctx,
+                               struct sss_domain_info *dom)
+{
+    if (dom == NULL) {
+        return ID_OUTSIDE;
+    }
+
+    if (strcasecmp(dom->provider, "local") == 0) {
+        return ID_IN_LOCAL;
+    } else if (strcasecmp(dom->provider, "files") == 0 ||
+               is_domain_local_legacy(ctx, dom) == 0) {
+        return ID_IN_LEGACY_LOCAL;
+    }
+
+    return ID_IN_OTHER;
+}
+
+static struct sss_domain_info *get_local_domain(struct tools_ctx *ctx)
+{
+    struct sss_domain_info *dom = NULL;
+
+    /* No ID specified, find LOCAL */
+    for (dom = ctx->domains; dom; dom = dom->next) {
+        if (strcasecmp(dom->provider, "local") == 0) {
+            break;
+        }
+    }
+
+    return dom;
+}
+
+int get_domain_by_id(struct tools_ctx *ctx,
+                     uint32_t id,
+                     struct sss_domain_info **_dom)
+{
+    struct sss_domain_info *dom = NULL;
+    int ret = EOK;
+
+    if (id) {
+        for (dom = ctx->domains; dom; dom = dom->next) {
+            if (id >= dom->id_min &&
+                (dom->id_max == 0 || id <= dom->id_max)) {
+                break;
+            }
+        }
+    }
+
+    if (dom == NULL && id == 0) {
+        dom = get_local_domain(ctx);
+        if (dom == NULL) {
+            DEBUG(1, ("Cannot find local domain info\n"));
+            ret = ENOENT;
+        }
+    }
+
+    *_dom = dom;
+    return ret;
+}
+
+int domain_lookup(struct ops_ctx *ops)
+{
+    int ret;
+    struct sss_domain_info *dom = NULL;
+
+    ret = get_domain_by_id(ops->ctx, ops->uid, &dom);
+    if (ret != EOK) {
+        return ret;
+    }
+    if (ops->domain && ops->uid && ops->domain != dom) {
+        return ret;
+    }
+    if (ops->domain == NULL && dom) {
+        ops->domain = dom;
+    }
+
+    return 0;
+}
+
+int setup_db(struct tools_ctx **tools_ctx)
+{
+    char *confdb_path;
+    struct tools_ctx *ctx;
+    int ret;
+
+    ctx = talloc_zero(NULL, struct tools_ctx);
+    if (ctx == NULL) {
+        DEBUG(1, ("Could not allocate memory for tools context"));
+        return ENOMEM;
+    }
+
+    /* Create the event context */
+    ctx->ev = tevent_context_init(ctx);
+    if (ctx->ev == NULL) {
+        DEBUG(1, ("Could not create event context"));
+        talloc_free(ctx);
+        return EIO;
+    }
+
+    confdb_path = talloc_asprintf(ctx, "%s/%s", DB_PATH, CONFDB_FILE);
+    if (confdb_path == NULL) {
+        talloc_free(ctx);
+        return ENOMEM;
+    }
+
+    /* Connect to the conf db */
+    ret = confdb_init(ctx, ctx->ev, &ctx->confdb, confdb_path);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not initialize connection to the confdb"));
+        talloc_free(ctx);
+        return ret;
+    }
+
+    ret = confdb_get_domains(ctx->confdb, ctx, &ctx->domains);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not get domains"));
+        talloc_free(ctx);
+        return ret;
+    }
+
+    /* open sysdb at default path */
+    ret = sysdb_init(ctx, ctx->ev, ctx->confdb, NULL, &ctx->sysdb);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not initialize connection to the sysdb"));
+        talloc_free(ctx);
+        return ret;
+    }
+
+    talloc_free(confdb_path);
+    *tools_ctx = ctx;
+    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);
+}
+
+/* FIXME: avoid using strtok !! */
+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_name_domain(struct ops_ctx *octx,
+                      const char *fullname)
+{
+    int ret;
+    char *domain = NULL;
+    struct sss_domain_info *dom;
+
+    ret = sss_parse_name(octx, octx->ctx->snctx, fullname, &domain, &octx->name);
+    if (ret != EOK) {
+        DEBUG(0, ("Cannot parse full name\n"));
+        return ret;
+    }
+    DEBUG(5, ("Parsed username: %s\n", octx->name));
+
+    if (domain) {
+        DEBUG(5, ("Parsed domain: %s\n", domain));
+
+        /* Got string domain name, find corresponding sss_domain_info */
+        for (dom = octx->ctx->domains; dom; dom = dom->next) {
+            if (strcasecmp(dom->name, domain) == 0) {
+                DEBUG(6, ("Found sss_domain_info for given domain name\n"));
+                octx->domain = dom;
+                break;
+            }
+        }
+        if (octx->domain == NULL) {
+            DEBUG(0, ("Invalid domain %s specified in FQDN\n", domain));
+            return EINVAL;
+        }
+    }
+
+    return EOK;
+}
+
+int run_command(const char *command)
+{
+    int ret;
+
+    ret = system(command);
+    if (ret) {
+        if (ret == -1) {
+            DEBUG(1, ("system(3) failed\n"));
+        } else {
+            DEBUG(1, ("Could not exec '%s', return code: %d\n",
+                      command, WEXITSTATUS(ret)));
+        }
+    }
+
+    return ret;
+}
+
+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 ops_ctx **_octx)
+{
+    int ret;
+    struct tools_ctx *tctx;
+    struct ops_ctx *octx;
+
+    /* Connect to the database */
+    ret = setup_db(&tctx);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not set up database\n"));
+        ret = EXIT_FAILURE;
+        goto fini;
+    }
+
+    ret = sss_names_init(tctx, tctx->confdb, &tctx->snctx);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not set up parsing\n"));
+        goto fini;
+    }
+
+    octx = talloc_zero(tctx, struct ops_ctx);
+    if (octx == NULL) {
+        DEBUG(1, ("Could not allocate memory for data context\n"));
+        ERROR("Out of memory\n");
+        ret = ENOMEM;
+        goto fini;
+    }
+    octx->ctx = tctx;
+    octx->ev = tctx->ev;
+
+    *_octx = octx;
+    ret = EOK;
+fini:
+    return ret;
+}
+
diff --git a/server/tools/common/tools_util.h b/server/tools/common/tools_util.h
new file mode 100644
index 0000000..604a6ac
--- /dev/null
+++ b/server/tools/common/tools_util.h
@@ -0,0 +1,68 @@
+#ifndef __TOOLS_UTIL_H__
+#define __TOOLS_UTIL_H__
+
+#include "util/sssd-i18n.h"
+#include "tools/common/sss_sync_ops.h"
+
+#define UID_NOT_SET 0
+#define GID_NOT_SET 0
+
+#define APPEND_PARAM(str, param, val) do { \
+    if (val) { \
+        str = talloc_asprintf_append(str, param, val); \
+        if (str == NULL) { \
+            return ENOMEM; \
+        } \
+    } \
+} while(0)
+
+#define APPEND_STRING(str, val) do { \
+    str = talloc_asprintf_append(str, "%s ", val); \
+    if (str == NULL) { \
+        return ENOMEM; \
+    } \
+} while(0)
+
+#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)
+
+enum id_domain {
+    ID_IN_LOCAL = 0,
+    ID_IN_LEGACY_LOCAL,
+    ID_IN_OTHER,
+    ID_OUTSIDE,
+    ID_ERROR
+};
+
+int init_sss_tools(struct ops_ctx **_octx);
+
+int setup_db(struct tools_ctx **ctx);
+
+void usage(poptContext pc, const char *error);
+
+int parse_groups(TALLOC_CTX *mem_ctx, const char *optstr, char ***_out);
+
+enum id_domain get_domain_type(struct tools_ctx *ctx,
+                               struct sss_domain_info *dom);
+
+int get_domain_by_id(struct tools_ctx *ctx,
+                     uint32_t id,
+                     struct sss_domain_info **_dom);
+
+int domain_lookup(struct ops_ctx *ops);
+
+int run_command(const char *command);
+
+int set_locale(void);
+
+int parse_name_domain(struct ops_ctx *octx,
+                      const char *fullname);
+
+#endif  /* __TOOLS_UTIL_H__ */
diff --git a/server/tools/sss_groupadd.c b/server/tools/sss_groupadd.c
index ab313fb..f433661 100644
--- a/server/tools/sss_groupadd.c
+++ b/server/tools/sss_groupadd.c
@@ -28,7 +28,7 @@
 
 #include "util/util.h"
 #include "db/sysdb.h"
-#include "tools/tools_util.h"
+#include "tools/common/tools_util.h"
 
 #ifndef GROUPADD
 #define GROUPADD SHADOW_UTILS_PATH"/groupadd "
@@ -42,72 +42,6 @@
 #define GROUPADD_GROUPNAME "%s "
 #endif
 
-static void add_group_req_done(struct tevent_req *req)
-{
-    struct ops_ctx *data = tevent_req_callback_data(req, struct ops_ctx);
-
-    data->error = sysdb_transaction_commit_recv(req);
-    data->done = true;
-}
-
-static void add_group_terminate(struct ops_ctx *data, int error)
-{
-    struct tevent_req *req;
-
-    if (error != EOK) {
-        goto fail;
-    }
-
-    req = sysdb_transaction_commit_send(data, data->ev, data->handle);
-    if (!req) {
-        error = ENOMEM;
-        goto fail;
-    }
-    tevent_req_set_callback(req, add_group_req_done, data);
-
-    return;
-
-fail:
-    /* free transaction */
-    talloc_zfree(data->handle);
-
-    data->error = error;
-    data->done = true;
-}
-
-static void add_group_done(struct tevent_req *subreq);
-
-static void add_group(struct tevent_req *req)
-{
-    struct ops_ctx *data = tevent_req_callback_data(req, struct ops_ctx);
-    struct tevent_req *subreq;
-    int ret;
-
-    ret = sysdb_transaction_recv(req, data, &data->handle);
-    if (ret != EOK) {
-        return add_group_terminate(data, ret);
-    }
-
-    subreq = sysdb_add_group_send(data, data->ev, data->handle,
-                                  data->domain, data->name,
-                                  data->gid, NULL);
-    if (!subreq) {
-        add_group_terminate(data, ENOMEM);
-    }
-    tevent_req_set_callback(subreq, add_group_done, data);
-}
-
-static void add_group_done(struct tevent_req *subreq)
-{
-    struct ops_ctx *data = tevent_req_callback_data(subreq, struct ops_ctx);
-    int ret;
-
-    ret = sysdb_add_group_recv(subreq);
-    talloc_zfree(subreq);
-
-    return add_group_terminate(data, ret);
-}
-
 static int groupadd_legacy(struct ops_ctx *ctx)
 {
     int ret = EOK;
@@ -141,7 +75,6 @@ int main(int argc, const char **argv)
         POPT_TABLEEND
     };
     poptContext pc = NULL;
-    struct tevent_req *req;
     struct ops_ctx *data = NULL;
     int ret = EXIT_SUCCESS;
     const char *pc_groupname = NULL;
@@ -239,19 +172,7 @@ int main(int argc, const char **argv)
             goto fini;
     }
 
-    /* add_group */
-    req = sysdb_transaction_send(data, data->ev, data->ctx->sysdb);
-    if (!req) {
-        DEBUG(1, ("Could not start transaction (%d)[%s]\n", ret, strerror(ret)));
-        ERROR("Transaction error. Could not add group.\n");
-        ret = EXIT_FAILURE;
-        goto fini;
-    }
-    tevent_req_set_callback(req, add_group, data);
-
-    while (!data->done) {
-        tevent_loop_once(data->ev);
-    }
+    groupadd(data, data);
 
     if (data->error) {
         ret = data->error;
diff --git a/server/tools/sss_groupdel.c b/server/tools/sss_groupdel.c
index 474ca2c..2c6d049 100644
--- a/server/tools/sss_groupdel.c
+++ b/server/tools/sss_groupdel.c
@@ -28,7 +28,7 @@
 
 #include "db/sysdb.h"
 #include "util/util.h"
-#include "tools/tools_util.h"
+#include "tools/common/tools_util.h"
 
 #ifndef GROUPDEL
 #define GROUPDEL SHADOW_UTILS_PATH"/groupdel "
@@ -39,79 +39,6 @@
 #endif
 
 
-static void groupdel_req_done(struct tevent_req *req)
-{
-    struct ops_ctx *data = tevent_req_callback_data(req, struct ops_ctx);
-
-    data->error = sysdb_transaction_commit_recv(req);
-    data->done = true;
-}
-
-/* sysdb callback */
-static void groupdel_done(void *pvt, int error, struct ldb_result *ignore)
-{
-    struct ops_ctx *data = talloc_get_type(pvt, struct ops_ctx);
-    struct tevent_req *req;
-
-    if (error != EOK) {
-        goto fail;
-    }
-
-    req = sysdb_transaction_commit_send(data, data->ev, data->handle);
-    if (!req) {
-        error = ENOMEM;
-        goto fail;
-    }
-    tevent_req_set_callback(req, groupdel_req_done, data);
-
-    return;
-
-fail:
-    /* free transaction */
-    talloc_zfree(data->handle);
-
-    data->error = error;
-    data->done = true;
-}
-
-static void group_del_done(struct tevent_req *subreq);
-
-static void group_del(struct tevent_req *req)
-{
-    struct ops_ctx *data = tevent_req_callback_data(req, struct ops_ctx);
-    struct tevent_req *subreq;
-    struct ldb_dn *group_dn;
-    int ret;
-
-    ret = sysdb_transaction_recv(req, data, &data->handle);
-    if (ret != EOK) {
-        return groupdel_done(data, ret, NULL);
-    }
-
-    group_dn = sysdb_group_dn(data->ctx->sysdb, data,
-                              data->domain->name, data->name);
-    if (group_dn == NULL) {
-        DEBUG(1, ("Could not construct a group DN\n"));
-        return groupdel_done(data, ENOMEM, NULL);
-    }
-
-    subreq = sysdb_delete_entry_send(data, data->ev, data->handle, group_dn, false);
-    if (!subreq)
-        return groupdel_done(data, ENOMEM, NULL);
-
-    tevent_req_set_callback(subreq, group_del_done, data);
-}
-
-static void group_del_done(struct tevent_req *subreq)
-{
-    struct ops_ctx *data = tevent_req_callback_data(subreq, struct ops_ctx);
-    int ret;
-
-    ret = sysdb_delete_entry_recv(subreq);
-
-    return groupdel_done(data, ret, NULL);
-}
-
 static int groupdel_legacy(struct ops_ctx *ctx)
 {
     int ret = EOK;
@@ -131,7 +58,6 @@ int main(int argc, const char **argv)
     int ret = EXIT_SUCCESS;
     int pc_debug = 0;
     struct ops_ctx *data = NULL;
-    struct tevent_req *req;
     struct group *grp_info;
     const char *pc_groupname = NULL;
 
@@ -238,18 +164,7 @@ int main(int argc, const char **argv)
     }
 
     /* groupdel */
-    req = sysdb_transaction_send(data, data->ev, data->ctx->sysdb);
-    if (!req) {
-        DEBUG(1, ("Could not start transaction (%d)[%s]\n", ret, strerror(ret)));
-        ERROR("Transaction error. Could not remove group.\n");
-        ret = EXIT_FAILURE;
-        goto fini;
-    }
-    tevent_req_set_callback(req, group_del, data);
-
-    while (!data->done) {
-        tevent_loop_once(data->ev);
-    }
+    groupdel(data, data);
 
     if (data->error) {
         ret = data->error;
diff --git a/server/tools/sss_groupmod.c b/server/tools/sss_groupmod.c
index e8c548b..4f681ea 100644
--- a/server/tools/sss_groupmod.c
+++ b/server/tools/sss_groupmod.c
@@ -29,7 +29,7 @@
 
 #include "util/util.h"
 #include "db/sysdb.h"
-#include "tools/tools_util.h"
+#include "tools/common/tools_util.h"
 
 #ifndef GROUPMOD
 #define GROUPMOD SHADOW_UTILS_PATH"/groupmod "
@@ -43,221 +43,6 @@
 #define GROUPMOD_GROUPNAME "%s "
 #endif
 
-static void mod_group_req_done(struct tevent_req *req)
-{
-    struct ops_ctx *data = tevent_req_callback_data(req, struct ops_ctx);
-
-    data->error = sysdb_transaction_commit_recv(req);
-    data->done = true;
-}
-
-static void mod_group_done(struct ops_ctx *data, int error)
-{
-    struct tevent_req *req;
-
-    if (error != EOK) {
-        goto fail;
-    }
-
-    req = sysdb_transaction_commit_send(data, data->ev, data->handle);
-    if (!req) {
-        error = ENOMEM;
-        goto fail;
-    }
-    tevent_req_set_callback(req, mod_group_req_done, data);
-
-    return;
-
-fail:
-    /* free transaction */
-    talloc_zfree(data->handle);
-
-    data->error = error;
-    data->done = true;
-}
-
-static void mod_group_attr_done(struct tevent_req *req);
-static void mod_group_cont(struct ops_ctx *data);
-static void remove_from_groups(struct ops_ctx *data);
-static void remove_from_groups_done(struct tevent_req *req);
-static void add_to_groups(struct ops_ctx *data);
-static void add_to_groups_done(struct tevent_req *req);
-
-static void mod_group(struct tevent_req *req)
-{
-    struct ops_ctx *data;
-    struct tevent_req *subreq;
-    struct sysdb_attrs *attrs;
-    int ret;
-
-    data = tevent_req_callback_data(req, struct ops_ctx);
-
-    ret = sysdb_transaction_recv(req, data, &data->handle);
-    if (ret != EOK) {
-        return mod_group_done(data, ret);
-    }
-    talloc_zfree(req);
-
-    if (data->gid != 0) {
-        attrs = sysdb_new_attrs(data);
-        if (!attrs) {
-            mod_group_done(data, ENOMEM);
-        }
-        ret = sysdb_attrs_add_uint32(attrs, SYSDB_GIDNUM, data->gid);
-        if (ret) {
-            mod_group_done(data, ret);
-        }
-
-        subreq = sysdb_set_group_attr_send(data, data->ev, data->handle,
-                                           data->domain, data->name,
-                                           attrs, SYSDB_MOD_REP);
-        if (!subreq) {
-            return mod_group_done(data, ENOMEM);
-        }
-        tevent_req_set_callback(subreq, mod_group_attr_done, data);
-        return;
-    }
-
-    return mod_group_cont(data);
-}
-
-static void mod_group_attr_done(struct tevent_req *subreq)
-{
-    struct ops_ctx *data = tevent_req_callback_data(subreq, struct ops_ctx);
-    int ret;
-
-    ret = sysdb_set_group_attr_recv(subreq);
-    talloc_zfree(subreq);
-    if (ret != EOK) {
-        return mod_group_done(data, ret);
-    }
-
-    mod_group_cont(data);
-}
-
-static void mod_group_cont(struct ops_ctx *data)
-{
-    if (data->rmgroups != NULL) {
-        return remove_from_groups(data);
-    }
-
-    if (data->addgroups != NULL) {
-        return add_to_groups(data);
-    }
-
-    return mod_group_done(data, EOK);
-}
-
-static void remove_from_groups(struct ops_ctx *data)
-{
-    struct ldb_dn *parent_dn;
-    struct ldb_dn *member_dn;
-    struct tevent_req *req;
-
-    parent_dn = sysdb_group_dn(data->ctx->sysdb, data,
-                               data->domain->name, data->name);
-    if (!parent_dn) {
-        return mod_group_done(data, ENOMEM);
-    }
-
-    member_dn = sysdb_group_dn(data->ctx->sysdb, data,
-                               data->domain->name,
-                               data->rmgroups[data->cur]);
-    if (!member_dn) {
-        return mod_group_done(data, ENOMEM);
-    }
-
-    req = sysdb_mod_group_member_send(data,
-                                      data->ev,
-                                      data->handle,
-                                      member_dn,
-                                      parent_dn,
-                                      LDB_FLAG_MOD_DELETE);
-    if (!req) {
-        return mod_group_done(data, ENOMEM);
-    }
-    tevent_req_set_callback(req, remove_from_groups_done, data);
-}
-
-static void remove_from_groups_done(struct tevent_req *req)
-{
-    struct ops_ctx *data = tevent_req_callback_data(req, struct ops_ctx);
-    int ret;
-
-    ret = sysdb_mod_group_member_recv(req);
-    if (ret) {
-        return mod_group_done(data, ret);
-    }
-    talloc_zfree(req);
-
-    /* go on to next group */
-    data->cur++;
-
-    /* check if we added all of them */
-    if (data->rmgroups[data->cur] == NULL) {
-        data->cur = 0;
-        if (data->addgroups != NULL) {
-            return remove_from_groups(data);
-        }
-        return mod_group_done(data, EOK);
-    }
-
-    return remove_from_groups(data);
-}
-
-static void add_to_groups(struct ops_ctx *data)
-{
-    struct ldb_dn *parent_dn;
-    struct ldb_dn *member_dn;
-    struct tevent_req *req;
-
-    parent_dn = sysdb_group_dn(data->ctx->sysdb, data,
-                               data->domain->name, data->name);
-    if (!parent_dn) {
-        return mod_group_done(data, ENOMEM);
-    }
-
-    member_dn = sysdb_group_dn(data->ctx->sysdb, data,
-                               data->domain->name,
-                               data->addgroups[data->cur]);
-    if (!member_dn) {
-        return mod_group_done(data, ENOMEM);
-    }
-
-    req = sysdb_mod_group_member_send(data,
-                                      data->ev,
-                                      data->handle,
-                                      member_dn,
-                                      parent_dn,
-                                      LDB_FLAG_MOD_ADD);
-    if (!req) {
-        return mod_group_done(data, ENOMEM);
-    }
-    tevent_req_set_callback(req, add_to_groups_done, data);
-}
-
-static void add_to_groups_done(struct tevent_req *req)
-{
-    struct ops_ctx *data = tevent_req_callback_data(req, struct ops_ctx);
-    int ret;
-
-    ret = sysdb_mod_group_member_recv(req);
-    if (ret) {
-        return mod_group_done(data, ret);
-    }
-    talloc_zfree(req);
-
-    /* go on to next group */
-    data->cur++;
-
-    /* check if we added all of them */
-    if (data->addgroups[data->cur] == NULL) {
-        return mod_group_done(data, EOK);
-    }
-
-    return add_to_groups(data);
-}
-
 static int groupmod_legacy(struct tools_ctx *tools_ctx,
                            struct ops_ctx *ctx,
                            struct sss_domain_info *old_domain)
@@ -316,7 +101,6 @@ int main(int argc, const char **argv)
     };
     poptContext pc = NULL;
     struct ops_ctx *data = NULL;
-    struct tevent_req *req;
     char *addgroups = NULL, *rmgroups = NULL;
     int ret;
     struct group *grp_info;
@@ -458,18 +242,7 @@ int main(int argc, const char **argv)
             goto fini;
     }
 
-    req = sysdb_transaction_send(data, data->ev, data->ctx->sysdb);
-    if (!req) {
-        DEBUG(1, ("Could not start transaction (%d)[%s]\n", ret, strerror(ret)));
-        ERROR("Transaction error. Could not modify group.\n");
-        ret = EXIT_FAILURE;
-        goto fini;
-    }
-    tevent_req_set_callback(req, mod_group, data);
-
-    while (!data->done) {
-        tevent_loop_once(data->ctx->ev);
-    }
+    groupmod(data, data);
 
     if (data->error) {
         ret = data->error;
diff --git a/server/tools/sss_useradd.c b/server/tools/sss_useradd.c
index 2d7ee2e..0cd5051 100644
--- a/server/tools/sss_useradd.c
+++ b/server/tools/sss_useradd.c
@@ -29,7 +29,7 @@
 
 #include "util/util.h"
 #include "db/sysdb.h"
-#include "tools/tools_util.h"
+#include "tools/common/tools_util.h"
 #include "util/sssd-i18n.h"
 
 /* Define default command strings if not redefined by user */
@@ -73,15 +73,6 @@
 #define USERADD_USERNAME "%s "
 #endif
 
-/* Default settings for user attributes */
-#define CONFDB_DFL_SECTION "config/user_defaults"
-
-#define DFL_SHELL_ATTR     "defaultShell"
-#define DFL_BASEDIR_ATTR   "baseDirectory"
-
-#define DFL_SHELL_VAL      "/bin/bash"
-#define DFL_BASEDIR_VAL    "/home"
-
 static void get_gid_callback(void *ptr, int error, struct ldb_result *res)
 {
     struct ops_ctx *data = talloc_get_type(ptr, struct ops_ctx);
@@ -148,133 +139,6 @@ done:
     return ret;
 }
 
-static void add_user_req_done(struct tevent_req *req)
-{
-    struct ops_ctx *data = tevent_req_callback_data(req, struct ops_ctx);
-
-    data->error = sysdb_transaction_commit_recv(req);
-    data->done = true;
-}
-
-static void add_user_terminate(struct ops_ctx *data, int error)
-{
-    struct tevent_req *req;
-
-    if (error != EOK) {
-        goto fail;
-    }
-
-    req = sysdb_transaction_commit_send(data, data->ev, data->handle);
-    if (!req) {
-        error = ENOMEM;
-        goto fail;
-    }
-    tevent_req_set_callback(req, add_user_req_done, data);
-
-    return;
-
-fail:
-    /* free transaction */
-    talloc_zfree(data->handle);
-
-    data->error = error;
-    data->done = true;
-}
-
-static void add_user_done(struct tevent_req *subreq);
-static void add_to_groups(struct ops_ctx *data);
-static void add_to_groups_done(struct tevent_req *req);
-
-static void add_user(struct tevent_req *req)
-{
-    struct ops_ctx *data = tevent_req_callback_data(req, struct ops_ctx);
-    struct tevent_req *subreq;
-    int ret;
-
-    ret = sysdb_transaction_recv(req, data, &data->handle);
-    if (ret != EOK) {
-        return add_user_terminate(data, ret);
-    }
-
-    subreq = sysdb_add_user_send(data, data->ev, data->handle,
-                                 data->domain, data->name,
-                                 data->uid, data->gid,
-                                 data->gecos, data->home,
-                                 data->shell, NULL);
-    if (!subreq) {
-        add_user_terminate(data, ENOMEM);
-    }
-    tevent_req_set_callback(subreq, add_user_done, data);
-}
-
-static void add_user_done(struct tevent_req *subreq)
-{
-    struct ops_ctx *data = tevent_req_callback_data(subreq, struct ops_ctx);
-    int ret;
-
-    ret = sysdb_add_user_recv(subreq);
-    talloc_zfree(subreq);
-    if (ret) {
-        return add_user_terminate(data, ret);
-    }
-
-    if (data->groups) {
-        return add_to_groups(data);
-    }
-
-    return add_user_terminate(data, ret);
-}
-
-static void add_to_groups(struct ops_ctx *data)
-{
-    struct ldb_dn *parent_dn;
-    struct ldb_dn *member_dn;
-    struct tevent_req *subreq;
-
-    member_dn = sysdb_user_dn(data->ctx->sysdb, data,
-                              data->domain->name, data->name);
-    if (!member_dn) {
-        return add_user_terminate(data, ENOMEM);
-    }
-
-    parent_dn = sysdb_group_dn(data->ctx->sysdb, data,
-                              data->domain->name,
-                              data->groups[data->cur]);
-    if (!parent_dn) {
-        return add_user_terminate(data, ENOMEM);
-    }
-
-    subreq = sysdb_mod_group_member_send(data, data->ev, data->handle,
-                                         member_dn, parent_dn,
-                                         LDB_FLAG_MOD_ADD);
-    if (!subreq) {
-        return add_user_terminate(data, ENOMEM);
-    }
-    tevent_req_set_callback(subreq, add_to_groups_done, data);
-}
-
-static void add_to_groups_done(struct tevent_req *subreq)
-{
-    struct ops_ctx *data = tevent_req_callback_data(subreq, struct ops_ctx);
-    int ret;
-
-    ret = sysdb_mod_group_member_recv(subreq);
-    talloc_zfree(subreq);
-    if (ret) {
-        return add_user_terminate(data, ret);
-    }
-
-    /* go on to next group */
-    data->cur++;
-
-    /* check if we added all of them */
-    if (data->groups[data->cur] == NULL) {
-        return add_user_terminate(data, EOK);
-    }
-
-    return add_to_groups(data);
-}
-
 static int useradd_legacy(struct ops_ctx *ctx, char *grouplist)
 {
     int ret = EOK;
@@ -316,7 +180,6 @@ int main(int argc, const char **argv)
     const char *pc_gecos = NULL;
     const char *pc_home = NULL;
     char *pc_shell = NULL;
-    char *basedir = NULL;
     int pc_debug = 0;
     const char *pc_username = NULL;
     struct poptOption long_options[] = {
@@ -333,7 +196,6 @@ int main(int argc, const char **argv)
     poptContext pc = NULL;
     struct sss_domain_info *dom = NULL;
     struct ops_ctx *data = NULL;
-    struct tevent_req *req;
     char *groups = NULL;
     int ret;
 
@@ -393,7 +255,7 @@ int main(int argc, const char **argv)
     }
 
     if (groups) {
-        ret = parse_groups(data, groups, &data->groups);
+        ret = parse_groups(data, groups, &data->addgroups);
         if (ret != EOK) {
             DEBUG(1, ("Cannot parse groups to add the user to\n"));
             ERROR("Internal error while parsing parameters\n");
@@ -415,49 +277,10 @@ int main(int argc, const char **argv)
 
     /*
      * Fills in defaults for ops_ctx user did not specify.
-     * FIXME - Should this originate from the confdb or another config?
      */
-    if (!pc_gecos) {
-        pc_gecos = data->name;
-    }
-    data->gecos = talloc_strdup(data, pc_gecos);
-    if (!data->gecos) {
-        ret = EXIT_FAILURE;
-        goto fini;
-    }
-
-    if (pc_home) {
-        data->home = talloc_strdup(data, pc_home);
-    } else {
-        ret = confdb_get_string(data->ctx->confdb, data,
-                                CONFDB_DFL_SECTION, DFL_BASEDIR_ATTR,
-                                DFL_BASEDIR_VAL, &basedir);
-        if (ret != EOK) {
-            ret = EXIT_FAILURE;
-            goto fini;
-        }
-        data->home = talloc_asprintf(data, "%s/%s", basedir, data->name);
-        if (!data->home) {
-            ret = EXIT_FAILURE;
-            goto fini;
-        }
-    }
-    if (!data->home) {
-        ret = EXIT_FAILURE;
-        goto fini;
-    }
-
-    if (!pc_shell) {
-        ret = confdb_get_string(data->ctx->confdb, data,
-                                CONFDB_DFL_SECTION, DFL_SHELL_ATTR,
-                                DFL_SHELL_VAL, &pc_shell);
-        if (ret != EOK) {
-            ret = EXIT_FAILURE;
-            goto fini;
-        }
-    }
-    data->shell = talloc_strdup(data, pc_shell);
-    if (!data->shell) {
+    ret = useradd_defaults(data, data, pc_gecos, pc_home, pc_shell);
+    if (ret != EOK) {
+        ERROR("Cannot set default values\n");
         ret = EXIT_FAILURE;
         goto fini;
     }
@@ -510,18 +333,7 @@ int main(int argc, const char **argv)
     }
 
     /* useradd */
-    req = sysdb_transaction_send(data, data->ctx->ev, data->ctx->sysdb);
-    if (!req) {
-        DEBUG(1, ("Could not start transaction (%d)[%s]\n", ret, strerror(ret)));
-        ERROR("Transaction error. Could not modify user.\n");
-        ret = EXIT_FAILURE;
-        goto fini;
-    }
-    tevent_req_set_callback(req, add_user, data);
-
-    while (!data->done) {
-        tevent_loop_once(data->ctx->ev);
-    }
+    ret = useradd(data, data);
 
     if (data->error) {
         ret = data->error;
diff --git a/server/tools/sss_userdel.c b/server/tools/sss_userdel.c
index 2b9a91d..38bb83b 100644
--- a/server/tools/sss_userdel.c
+++ b/server/tools/sss_userdel.c
@@ -28,7 +28,7 @@
 
 #include "db/sysdb.h"
 #include "util/util.h"
-#include "tools/tools_util.h"
+#include "tools/common/tools_util.h"
 
 #ifndef USERDEL
 #define USERDEL SHADOW_UTILS_PATH"/userdel "
@@ -38,81 +38,6 @@
 #define USERDEL_USERNAME    "%s "
 #endif
 
-static void userdel_req_done(struct tevent_req *req)
-{
-    struct ops_ctx *data = tevent_req_callback_data(req, struct ops_ctx);
-
-    data->error = sysdb_transaction_commit_recv(req);
-    data->done = true;
-}
-
-/* sysdb callback */
-static void userdel_done(void *pvt, int error, struct ldb_result *ignore)
-{
-    struct ops_ctx *data = talloc_get_type(pvt, struct ops_ctx);
-    struct tevent_req *req;
-
-    if (error != EOK) {
-        goto fail;
-    }
-
-    req = sysdb_transaction_commit_send(data, data->ev, data->handle);
-    if (!req) {
-        error = ENOMEM;
-        goto fail;
-    }
-    tevent_req_set_callback(req, userdel_req_done, data);
-
-    return;
-
-fail:
-    /* free transaction */
-    talloc_zfree(data->handle);
-
-    data->error = error;
-    data->done = true;
-}
-
-static void user_del_done(struct tevent_req *subreq);
-
-static void user_del(struct tevent_req *req)
-{
-    struct ops_ctx *data;
-    struct tevent_req *subreq;
-    struct ldb_dn *user_dn;
-    int ret;
-
-    data = tevent_req_callback_data(req, struct ops_ctx);
-
-    ret = sysdb_transaction_recv(req, data, &data->handle);
-    if (ret != EOK) {
-        return userdel_done(data, ret, NULL);
-    }
-
-    user_dn = sysdb_user_dn(data->ctx->sysdb, data,
-                            data->domain->name, data->name);
-    if (!user_dn) {
-        DEBUG(1, ("Could not construct a user DN\n"));
-        return userdel_done(data, ENOMEM, NULL);
-    }
-
-    subreq = sysdb_delete_entry_send(data, data->ev, data->handle, user_dn, false);
-    if (!subreq)
-        return userdel_done(data, ENOMEM, NULL);
-
-    tevent_req_set_callback(subreq, user_del_done, data);
-}
-
-static void user_del_done(struct tevent_req *subreq)
-{
-    struct ops_ctx *data = tevent_req_callback_data(subreq, struct ops_ctx);
-    int ret;
-
-    ret = sysdb_delete_entry_recv(subreq);
-
-    return userdel_done(data, ret, NULL);
-}
-
 static int userdel_legacy(struct ops_ctx *ctx)
 {
     int ret = EOK;
@@ -131,8 +56,6 @@ int main(int argc, const char **argv)
 {
     int ret = EXIT_SUCCESS;
     struct ops_ctx *data = NULL;
-    struct tevent_req *req;
-    struct sss_domain_info *dom;
     struct passwd *pwd_info;
     const char *pc_username = NULL;
 
@@ -240,18 +163,7 @@ int main(int argc, const char **argv)
     }
 
     /* userdel */
-    req = sysdb_transaction_send(data, data->ev, data->ctx->sysdb);
-    if (!req) {
-        DEBUG(1, ("Could not start transaction (%d)[%s]\n", ret, strerror(ret)));
-        ERROR("Transaction error. Could not remove user.\n");
-        ret = EXIT_FAILURE;
-        goto fini;
-    }
-    tevent_req_set_callback(req, user_del, data);
-
-    while (!data->done) {
-        tevent_loop_once(data->ctx->ev);
-    }
+    userdel(data, data);
 
     if (data->error) {
         ret = data->error;
diff --git a/server/tools/sss_usermod.c b/server/tools/sss_usermod.c
index d515618..23ae3cc 100644
--- a/server/tools/sss_usermod.c
+++ b/server/tools/sss_usermod.c
@@ -29,19 +29,7 @@
 
 #include "util/util.h"
 #include "db/sysdb.h"
-#include "tools/tools_util.h"
-
-#define DO_LOCK     1
-#define DO_UNLOCK   2
-
-#define VAR_CHECK(var, val, attr, msg) do { \
-        if (var != (val)) { \
-            DEBUG(1, (msg" attribute: %s", attr)); \
-            ERROR(msg); \
-            var = EXIT_FAILURE; \
-            goto fini; \
-        } \
-} while(0)
+#include "tools/common/tools_util.h"
 
 /* Define default command strings if not redefined by user */
 #ifndef USERMOD
@@ -80,210 +68,6 @@
 #define USERMOD_USERNAME "%s"
 #endif
 
-static void mod_user_req_done(struct tevent_req *req)
-{
-    struct ops_ctx *data = tevent_req_callback_data(req, struct ops_ctx);
-
-    data->error = sysdb_transaction_commit_recv(req);
-    data->done = true;
-}
-
-static void mod_user_done(struct ops_ctx *data, int error)
-{
-    struct tevent_req *req;
-
-    if (error != EOK) {
-        goto fail;
-    }
-
-    req = sysdb_transaction_commit_send(data, data->ev, data->handle);
-    if (!req) {
-        error = ENOMEM;
-        goto fail;
-    }
-    tevent_req_set_callback(req, mod_user_req_done, data);
-
-    return;
-
-fail:
-    /* free transaction */
-    talloc_zfree(data->handle);
-
-    data->error = error;
-    data->done = true;
-}
-
-static void mod_user_attr_done(struct tevent_req *req);
-static void mod_user_cont(struct ops_ctx *data);
-static void remove_from_groups(struct ops_ctx *data);
-static void remove_from_groups_done(struct tevent_req *req);
-static void add_to_groups(struct ops_ctx *data);
-static void add_to_groups_done(struct tevent_req *req);
-
-static void mod_user(struct tevent_req *req)
-{
-    struct ops_ctx *data;
-    struct tevent_req *subreq;
-    int ret;
-
-    data = tevent_req_callback_data(req, struct ops_ctx);
-
-    ret = sysdb_transaction_recv(req, data, &data->handle);
-    if (ret != EOK) {
-        return mod_user_done(data, ret);
-    }
-
-    if (data->attrs->num != 0) {
-        subreq = sysdb_set_user_attr_send(data, data->ev, data->handle,
-                                          data->domain, data->name,
-                                          data->attrs, SYSDB_MOD_REP);
-        if (!subreq) {
-            return mod_user_done(data, ENOMEM);
-        }
-        tevent_req_set_callback(subreq, mod_user_attr_done, data);
-        return;
-    }
-
-    return mod_user_cont(data);
-}
-
-static void mod_user_attr_done(struct tevent_req *subreq)
-{
-    struct ops_ctx *data = tevent_req_callback_data(subreq, struct ops_ctx);
-    int ret;
-
-    ret = sysdb_set_user_attr_recv(subreq);
-    talloc_zfree(subreq);
-    if (ret != EOK) {
-        return mod_user_done(data, ret);
-    }
-
-    mod_user_cont(data);
-}
-
-static void mod_user_cont(struct ops_ctx *data)
-{
-    if (data->rmgroups != NULL) {
-        return remove_from_groups(data);
-    }
-
-    if (data->addgroups != NULL) {
-        return add_to_groups(data);
-    }
-
-    return mod_user_done(data, EOK);
-}
-
-static void remove_from_groups(struct ops_ctx *data)
-{
-    struct ldb_dn *parent_dn;
-    struct ldb_dn *member_dn;
-    struct tevent_req *req;
-
-    member_dn = sysdb_user_dn(data->ctx->sysdb, data,
-                              data->domain->name, data->name);
-    if (!member_dn) {
-        return mod_user_done(data, ENOMEM);
-    }
-
-    parent_dn = sysdb_group_dn(data->ctx->sysdb, data,
-                               data->domain->name,
-                               data->rmgroups[data->cur]);
-    if (!parent_dn) {
-        return mod_user_done(data, ENOMEM);
-    }
-
-    req = sysdb_mod_group_member_send(data,
-                                      data->ev,
-                                      data->handle,
-                                      member_dn,
-                                      parent_dn,
-                                      LDB_FLAG_MOD_DELETE);
-    if (!req) {
-        return mod_user_done(data, ENOMEM);
-    }
-    tevent_req_set_callback(req, remove_from_groups_done, data);
-}
-
-static void remove_from_groups_done(struct tevent_req *req)
-{
-    struct ops_ctx *data = tevent_req_callback_data(req, struct ops_ctx);
-    int ret;
-
-    ret = sysdb_mod_group_member_recv(req);
-    if (ret) {
-        return mod_user_done(data, ret);
-    }
-    talloc_zfree(req);
-
-    /* go on to next group */
-    data->cur++;
-
-    /* check if we added all of them */
-    if (data->rmgroups[data->cur] == NULL) {
-        data->cur = 0;
-        if (data->addgroups != NULL) {
-            return remove_from_groups(data);
-        }
-        return mod_user_done(data, EOK);
-    }
-
-    return remove_from_groups(data);
-}
-
-static void add_to_groups(struct ops_ctx *data)
-{
-    struct ldb_dn *parent_dn;
-    struct ldb_dn *member_dn;
-    struct tevent_req *req;
-
-    member_dn = sysdb_user_dn(data->ctx->sysdb, data,
-                              data->domain->name, data->name);
-    if (!member_dn) {
-        return mod_user_done(data, ENOMEM);
-    }
-
-    parent_dn = sysdb_group_dn(data->ctx->sysdb, data,
-                               data->domain->name,
-                               data->addgroups[data->cur]);
-    if (!parent_dn) {
-        return mod_user_done(data, ENOMEM);
-    }
-
-    req = sysdb_mod_group_member_send(data,
-                                      data->ev,
-                                      data->handle,
-                                      member_dn,
-                                      parent_dn,
-                                      LDB_FLAG_MOD_ADD);
-    if (!req) {
-        return mod_user_done(data, ENOMEM);
-    }
-    tevent_req_set_callback(req, add_to_groups_done, data);
-}
-
-static void add_to_groups_done(struct tevent_req *req)
-{
-    struct ops_ctx *data = tevent_req_callback_data(req, struct ops_ctx);
-    int ret;
-
-    ret = sysdb_mod_group_member_recv(req);
-    if (ret) {
-        return mod_user_done(data, ret);
-    }
-    talloc_zfree(req);
-
-    /* go on to next group */
-    data->cur++;
-
-    /* check if we added all of them */
-    if (data->addgroups[data->cur] == NULL) {
-        return mod_user_done(data, EOK);
-    }
-
-    return add_to_groups(data);
-}
-
 static int usermod_legacy(struct tools_ctx *tools_ctx, struct ops_ctx *ctx,
                           uid_t uid, gid_t gid,
                           const char *gecos, const char *home,
@@ -373,7 +157,6 @@ int main(int argc, const char **argv)
     };
     poptContext pc = NULL;
     struct ops_ctx *data = NULL;
-    struct tevent_req *req;
     char *addgroups = NULL, *rmgroups = NULL;
     int ret;
     struct passwd *pwd_info;
@@ -529,77 +312,21 @@ int main(int argc, const char **argv)
     }
 
     /* add parameters to changeset */
-    /* FIXME - might want to do this via attr:pc_var mapping in a loop */
-
-    if(pc_shell) {
-        ret = sysdb_attrs_add_string(data->attrs,
-                                     SYSDB_SHELL,
-                                     pc_shell);
-        VAR_CHECK(ret, EOK, SYSDB_SHELL,
-                  "Could not add attribute to changeset\n");
-    }
-
-    if(pc_home) {
-        ret = sysdb_attrs_add_string(data->attrs,
-                                     SYSDB_HOMEDIR,
-                                     pc_home);
-        VAR_CHECK(ret, EOK, SYSDB_HOMEDIR,
-                  "Could not add attribute to changeset\n");
-    }
-
-    if(pc_gecos) {
-        ret = sysdb_attrs_add_string(data->attrs,
-                                     SYSDB_GECOS,
-                                     pc_gecos);
-        VAR_CHECK(ret, EOK, SYSDB_GECOS,
-                  "Could not add attribute to changeset\n");
-    }
-
-    if(pc_uid) {
-        ret = sysdb_attrs_add_long(data->attrs,
-                                   SYSDB_UIDNUM,
-                                   pc_uid);
-        VAR_CHECK(ret, EOK, SYSDB_UIDNUM,
-                  "Could not add attribute to changeset\n");
-    }
-
-    if(pc_gid) {
-        ret = sysdb_attrs_add_long(data->attrs,
-                                   SYSDB_GIDNUM,
-                                   pc_gid);
-        VAR_CHECK(ret, EOK, SYSDB_GIDNUM,
-                  "Could not add attribute to changeset\n");
-    }
-
-    if(pc_lock == DO_LOCK) {
-        ret = sysdb_attrs_add_string(data->attrs,
-                                     SYSDB_DISABLED,
-                                     "true");
-        VAR_CHECK(ret, EOK, SYSDB_DISABLED,
-                  "Could not add attribute to changeset\n");
-    }
-
-    if(pc_lock == DO_UNLOCK) {
-        /* PAM code checks for 'false' value in SYSDB_DISABLED attribute */
-        ret = sysdb_attrs_add_string(data->attrs,
-                                     SYSDB_DISABLED,
-                                     "false");
-        VAR_CHECK(ret, EOK, SYSDB_DISABLED,
-                  "Could not add attribute to changeset\n");
-    }
-
-    req = sysdb_transaction_send(data, data->ev, data->ctx->sysdb);
-    if (!req) {
-        DEBUG(1, ("Could not start transaction (%d)[%s]\n", ret, strerror(ret)));
-        ERROR("Transaction error. Could not modify user.\n");
+    ret = usermod_to_attrs(data, data,
+                           pc_gecos,
+                           pc_home,
+                           pc_shell,
+                           pc_uid,
+                           pc_gid,
+                           pc_lock);
+    if (ret != EOK) {
+        ERROR("Could not add attribute to changeset");
         ret = EXIT_FAILURE;
         goto fini;
     }
-    tevent_req_set_callback(req, mod_user, data);
 
-    while (!data->done) {
-        tevent_loop_once(data->ctx->ev);
-    }
+    /* usermod */
+    usermod(data, data);
 
     if (data->error) {
         ret = data->error;
diff --git a/server/tools/tools_util.c b/server/tools/tools_util.c
deleted file mode 100644
index 8d0ae83..0000000
--- a/server/tools/tools_util.c
+++ /dev/null
@@ -1,370 +0,0 @@
-/*
-   SSSD
-
-   tools_utils.c
-
-   Copyright (C) Jakub Hrozek <jhrozek@redhat.com>        2009
-
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 3 of the License, or
-   (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include <talloc.h>
-#include <tevent.h>
-#include <popt.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-
-#include "util/util.h"
-#include "confdb/confdb.h"
-#include "db/sysdb.h"
-#include "tools/tools_util.h"
-
-/*
- * Returns:
- * 0 = yes, local domain proxying to files
- * -1 = no, other type of domain
- * > 0 = error code
- */
-static int is_domain_local_legacy(struct tools_ctx *ctx, struct sss_domain_info *dom)
-{
-    char *libname = NULL;
-    char *conf_path = NULL;
-    int ret = -1;
-
-    /* Is there a better way to find out? Having LEGACYLOCAL as reserved would help */
-    conf_path = talloc_asprintf(ctx, "config/domains/%s", dom->name);
-    if (conf_path == NULL ) {
-        return ENOMEM;
-    }
-
-    ret = confdb_get_string(ctx->confdb, ctx, conf_path,
-                            "libName", NULL, &libname);
-    if (ret != EOK) {
-        talloc_free(conf_path);
-        return ret;
-    }
-    if (libname == NULL) {
-        talloc_free(conf_path);
-        return -1;
-    }
-
-    if (strcasecmp(libname, "files") == 0) {
-        talloc_free(conf_path);
-        talloc_free(libname);
-        return EOK;
-    }
-
-    talloc_free(conf_path);
-    talloc_free(libname);
-    return -1;
-}
-
-enum id_domain get_domain_type(struct tools_ctx *ctx,
-                               struct sss_domain_info *dom)
-{
-    if (dom == NULL) {
-        return ID_OUTSIDE;
-    }
-
-    if (strcasecmp(dom->provider, "local") == 0) {
-        return ID_IN_LOCAL;
-    } else if (strcasecmp(dom->provider, "files") == 0 ||
-               is_domain_local_legacy(ctx, dom) == 0) {
-        return ID_IN_LEGACY_LOCAL;
-    }
-
-    return ID_IN_OTHER;
-}
-
-static struct sss_domain_info *get_local_domain(struct tools_ctx *ctx)
-{
-    struct sss_domain_info *dom = NULL;
-
-    /* No ID specified, find LOCAL */
-    for (dom = ctx->domains; dom; dom = dom->next) {
-        if (strcasecmp(dom->provider, "local") == 0) {
-            break;
-        }
-    }
-
-    return dom;
-}
-
-int get_domain_by_id(struct tools_ctx *ctx,
-                     uint32_t id,
-                     struct sss_domain_info **_dom)
-{
-    struct sss_domain_info *dom = NULL;
-    int ret = EOK;
-
-    if (id) {
-        for (dom = ctx->domains; dom; dom = dom->next) {
-            if (id >= dom->id_min &&
-                (dom->id_max == 0 || id <= dom->id_max)) {
-                break;
-            }
-        }
-    }
-
-    if (dom == NULL && id == 0) {
-        dom = get_local_domain(ctx);
-        if (dom == NULL) {
-            DEBUG(1, ("Cannot find local domain info\n"));
-            ret = ENOENT;
-        }
-    }
-
-    *_dom = dom;
-    return ret;
-}
-
-int domain_lookup(struct ops_ctx *ops)
-{
-    int ret;
-    struct sss_domain_info *dom = NULL;
-
-    ret = get_domain_by_id(ops->ctx, ops->uid, &dom);
-    if (ret != EOK) {
-        return ret;
-    }
-    if (ops->domain && ops->uid && ops->domain != dom) {
-        return ret;
-    }
-    if (ops->domain == NULL && dom) {
-        ops->domain = dom;
-    }
-
-    return 0;
-}
-
-int setup_db(struct tools_ctx **tools_ctx)
-{
-    char *confdb_path;
-    struct tools_ctx *ctx;
-    int ret;
-
-    ctx = talloc_zero(NULL, struct tools_ctx);
-    if (ctx == NULL) {
-        DEBUG(1, ("Could not allocate memory for tools context"));
-        return ENOMEM;
-    }
-
-    /* Create the event context */
-    ctx->ev = tevent_context_init(ctx);
-    if (ctx->ev == NULL) {
-        DEBUG(1, ("Could not create event context"));
-        talloc_free(ctx);
-        return EIO;
-    }
-
-    confdb_path = talloc_asprintf(ctx, "%s/%s", DB_PATH, CONFDB_FILE);
-    if (confdb_path == NULL) {
-        talloc_free(ctx);
-        return ENOMEM;
-    }
-
-    /* Connect to the conf db */
-    ret = confdb_init(ctx, ctx->ev, &ctx->confdb, confdb_path);
-    if (ret != EOK) {
-        DEBUG(1, ("Could not initialize connection to the confdb"));
-        talloc_free(ctx);
-        return ret;
-    }
-
-    ret = confdb_get_domains(ctx->confdb, ctx, &ctx->domains);
-    if (ret != EOK) {
-        DEBUG(1, ("Could not get domains"));
-        talloc_free(ctx);
-        return ret;
-    }
-
-    /* open sysdb at default path */
-    ret = sysdb_init(ctx, ctx->ev, ctx->confdb, NULL, &ctx->sysdb);
-    if (ret != EOK) {
-        DEBUG(1, ("Could not initialize connection to the sysdb"));
-        talloc_free(ctx);
-        return ret;
-    }
-
-    talloc_free(confdb_path);
-    *tools_ctx = ctx;
-    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);
-}
-
-/* FIXME: avoid using strtok !! */
-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_name_domain(struct ops_ctx *octx,
-                      const char *fullname)
-{
-    int ret;
-    char *domain = NULL;
-    struct sss_domain_info *dom;
-
-    ret = sss_parse_name(octx, octx->ctx->snctx, fullname, &domain, &octx->name);
-    if (ret != EOK) {
-        DEBUG(0, ("Cannot parse full name\n"));
-        return ret;
-    }
-    DEBUG(5, ("Parsed username: %s\n", octx->name));
-
-    if (domain) {
-        DEBUG(5, ("Parsed domain: %s\n", domain));
-
-        /* Got string domain name, find corresponding sss_domain_info */
-        for (dom = octx->ctx->domains; dom; dom = dom->next) {
-            if (strcasecmp(dom->name, domain) == 0) {
-                DEBUG(6, ("Found sss_domain_info for given domain name\n"));
-                octx->domain = dom;
-                break;
-            }
-        }
-        if (octx->domain == NULL) {
-            DEBUG(0, ("Invalid domain %s specified in FQDN\n", domain));
-            return EINVAL;
-        }
-    }
-
-    return EOK;
-}
-
-int run_command(const char *command)
-{
-    int ret;
-
-    ret = system(command);
-    if (ret) {
-        if (ret == -1) {
-            DEBUG(1, ("system(3) failed\n"));
-        } else {
-            DEBUG(1, ("Could not exec '%s', return code: %d\n",
-                      command, WEXITSTATUS(ret)));
-        }
-    }
-
-    return ret;
-}
-
-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 ops_ctx **_octx)
-{
-    int ret;
-    struct tools_ctx *tctx;
-    struct ops_ctx *octx;
-
-    /* Connect to the database */
-    ret = setup_db(&tctx);
-    if (ret != EOK) {
-        DEBUG(1, ("Could not set up database\n"));
-        ret = EXIT_FAILURE;
-        goto fini;
-    }
-
-    ret = sss_names_init(tctx, tctx->confdb, &tctx->snctx);
-    if (ret != EOK) {
-        DEBUG(1, ("Could not set up parsing\n"));
-        goto fini;
-    }
-
-    octx = talloc_zero(tctx, struct ops_ctx);
-    if (octx == NULL) {
-        DEBUG(1, ("Could not allocate memory for data context\n"));
-        ERROR("Out of memory\n");
-        ret = ENOMEM;
-        goto fini;
-    }
-    octx->ctx = tctx;
-    octx->ev = tctx->ev;
-
-    *_octx = octx;
-    ret = EOK;
-fini:
-    return ret;
-}
-
diff --git a/server/tools/tools_util.h b/server/tools/tools_util.h
deleted file mode 100644
index 616608f..0000000
--- a/server/tools/tools_util.h
+++ /dev/null
@@ -1,99 +0,0 @@
-#ifndef __TOOLS_UTIL_H__
-#define __TOOLS_UTIL_H__
-
-#include "util/sssd-i18n.h"
-
-#define UID_NOT_SET 0
-#define GID_NOT_SET 0
-
-#define APPEND_PARAM(str, param, val) do { \
-    if (val) { \
-        str = talloc_asprintf_append(str, param, val); \
-        if (str == NULL) { \
-            return ENOMEM; \
-        } \
-    } \
-} while(0)
-
-#define APPEND_STRING(str, val) do { \
-    str = talloc_asprintf_append(str, "%s ", val); \
-    if (str == NULL) { \
-        return ENOMEM; \
-    } \
-} while(0)
-
-#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)
-
-enum id_domain {
-    ID_IN_LOCAL = 0,
-    ID_IN_LEGACY_LOCAL,
-    ID_IN_OTHER,
-    ID_OUTSIDE,
-    ID_ERROR
-};
-
-struct tools_ctx {
-    struct tevent_context *ev;
-    struct confdb_ctx *confdb;
-    struct sysdb_ctx *sysdb;
-    struct sss_names_ctx *snctx;
-
-    struct sss_domain_info *domains;
-};
-
-struct ops_ctx {
-    struct tools_ctx *ctx;
-    struct tevent_context *ev;
-    struct sss_domain_info *domain;
-
-    char *name;
-    uid_t uid;
-    gid_t gid;
-    char *gecos;
-    char *home;
-    char *shell;
-    struct sysdb_attrs *attrs;
-
-    char **addgroups;
-    char **rmgroups;
-    char **groups;
-    int cur;
-
-    struct sysdb_handle *handle;
-    int error;
-    bool done;
-};
-
-int init_sss_tools(struct ops_ctx **_octx);
-
-int setup_db(struct tools_ctx **ctx);
-
-void usage(poptContext pc, const char *error);
-
-int parse_groups(TALLOC_CTX *mem_ctx, const char *optstr, char ***_out);
-
-enum id_domain get_domain_type(struct tools_ctx *ctx,
-                               struct sss_domain_info *dom);
-
-int get_domain_by_id(struct tools_ctx *ctx,
-                     uint32_t id,
-                     struct sss_domain_info **_dom);
-
-int domain_lookup(struct ops_ctx *ops);
-
-int run_command(const char *command);
-
-int set_locale(void);
-
-int parse_name_domain(struct ops_ctx *octx,
-                      const char *fullname);
-
-#endif  /* __TOOLS_UTIL_H__ */
-- 
1.6.2.5

