>From 4c6f43bf80fd6e6a4a957126b18678c4656c16ac Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Mon, 14 Sep 2009 23:03:35 +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          |    2 +
 server/tools/sss_groupadd.c |  103 ++--
 server/tools/sss_groupdel.c |  111 ++---
 server/tools/sss_groupmod.c |  267 ++-------
 server/tools/sss_sync_ops.c | 1467 +++++++++++++++++++++++++++++++++++++++++++
 server/tools/sss_sync_ops.h |   86 +++
 server/tools/sss_useradd.c  |  248 ++------
 server/tools/sss_userdel.c  |  111 ++---
 server/tools/sss_usermod.c  |  347 ++---------
 server/tools/tools_util.c   |   89 +--
 server/tools/tools_util.h   |   34 +-
 11 files changed, 1845 insertions(+), 1020 deletions(-)
 create mode 100644 server/tools/sss_sync_ops.c
 create mode 100644 server/tools/sss_sync_ops.h

diff --git a/server/Makefile.am b/server/Makefile.am
index 61145f3..6036110 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -173,6 +173,7 @@ SSSD_RESPONDER_OBJ = \
     responder/common/responder_packet.c
 
 SSSD_TOOLS_OBJ = \
+    tools/sss_sync_ops.c \
     tools/tools_util.c
 
 SSSD_RESOLV_OBJ = \
@@ -233,6 +234,7 @@ dist_noinst_HEADERS = \
     providers/ldap/sdap.h \
     providers/ldap/sdap_async.h \
     tools/tools_util.h \
+    tools/sss_sync_ops.h \
     resolv/async_resolv.h \
     resolv/ares/ares_parse_srv_reply.h \
     resolv/ares/ares_parse_txt_reply.h
diff --git a/server/tools/sss_groupadd.c b/server/tools/sss_groupadd.c
index 830ba79..f528730 100644
--- a/server/tools/sss_groupadd.c
+++ b/server/tools/sss_groupadd.c
@@ -30,70 +30,40 @@
 #include "db/sysdb.h"
 #include "tools/tools_util.h"
 
-static void add_group_req_done(struct tevent_req *req)
+static void add_group_transaction(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;
-}
+    int ret;
+    struct tools_ctx *tctx = tevent_req_callback_data(req,
+                                                struct tools_ctx);
+    struct tevent_req *subreq;
 
-static void add_group_terminate(struct ops_ctx *data, int error)
-{
-    struct tevent_req *req;
+    ret = sysdb_transaction_recv(req, tctx, &tctx->handle);
+    if (ret) {
+        tevent_req_error(req, ret);
+        return;
+    }
+    talloc_zfree(req);
 
-    if (error != EOK) {
+    /* groupadd */
+    ret = groupadd(tctx, tctx->ev,
+                  tctx->sysdb, tctx->handle, tctx->octx);
+    if (ret != EOK) {
         goto fail;
     }
 
-    req = sysdb_transaction_commit_send(data, data->ctx->ev, data->handle);
-    if (!req) {
-        error = ENOMEM;
+    subreq = sysdb_transaction_commit_send(tctx, tctx->ev, tctx->handle);
+    if (!subreq) {
+        ret = ENOMEM;
         goto fail;
     }
-    tevent_req_set_callback(req, add_group_req_done, data);
-
+    tevent_req_set_callback(subreq, tools_transaction_done, tctx);
     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->ctx->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);
+    /* free transaction and signal error */
+    talloc_zfree(tctx->handle);
+    tctx->transaction_done = true;
+    tctx->error = ret;
 }
 
 int main(int argc, const char **argv)
@@ -109,10 +79,10 @@ int main(int argc, const char **argv)
         POPT_TABLEEND
     };
     poptContext pc = NULL;
-    struct tevent_req *req;
-    struct ops_ctx *data = NULL;
+    struct tools_ctx *tctx = NULL;
     int ret = EXIT_SUCCESS;
     const char *pc_groupname = NULL;
+    struct tevent_req *req;
 
     debug_prg_name = argv[0];
 
@@ -145,7 +115,7 @@ int main(int argc, const char **argv)
 
     CHECK_ROOT(ret, debug_prg_name);
 
-    ret = init_sss_tools(&data);
+    ret = init_sss_tools(&tctx);
     if (ret != EOK) {
         DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
         ERROR("Error initializing the tools\n");
@@ -153,38 +123,39 @@ int main(int argc, const char **argv)
         goto fini;
     }
 
-    ret = get_domain(data, pc_groupname);
+    /* 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("Cannot get domain information\n");
         ret = EXIT_FAILURE;
         goto fini;
     }
 
-    data->gid = pc_gid;
+    tctx->octx->gid = pc_gid;
 
     /* arguments processed, go on to actual work */
-    if (id_in_range(data->gid, data->domain) != EOK) {
+    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;
     }
 
     /* add_group */
-    req = sysdb_transaction_send(data, data->ctx->ev, data->ctx->sysdb);
+    req = sysdb_transaction_send(tctx->octx, tctx->ev, tctx->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);
+    tevent_req_set_callback(req, add_group_transaction, tctx);
 
-    while (!data->done) {
-        tevent_loop_once(data->ctx->ev);
+    while (!tctx->transaction_done) {
+        tevent_loop_once(tctx->ev);
     }
 
-    if (data->error) {
-        ret = data->error;
+    if (tctx->error) {
+        ret = tctx->error;
         switch (ret) {
             case EEXIST:
                 ERROR("A group with the same name or GID already exists\n");
@@ -201,7 +172,7 @@ int main(int argc, const char **argv)
 
     ret = EXIT_SUCCESS;
 fini:
-    talloc_free(data);
+    talloc_free(tctx);
     poptFreeContext(pc);
     exit(ret);
 }
diff --git a/server/tools/sss_groupdel.c b/server/tools/sss_groupdel.c
index 7d8f541..6677eb9 100644
--- a/server/tools/sss_groupdel.c
+++ b/server/tools/sss_groupdel.c
@@ -30,87 +30,50 @@
 #include "util/util.h"
 #include "tools/tools_util.h"
 
-static void groupdel_req_done(struct tevent_req *req)
+static void del_group_transaction(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;
-}
+    int ret;
+    struct tools_ctx *tctx = tevent_req_callback_data(req,
+                                                struct tools_ctx);
+    struct tevent_req *subreq;
 
-/* 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;
+    ret = sysdb_transaction_recv(req, tctx, &tctx->handle);
+    if (ret) {
+        tevent_req_error(req, ret);
+        return;
+    }
+    talloc_zfree(req);
 
-    if (error != EOK) {
+    /* groupdel */
+    ret = groupdel(tctx, tctx->ev,
+                   tctx->sysdb, tctx->handle, tctx->octx);
+    if (ret != EOK) {
         goto fail;
     }
 
-    req = sysdb_transaction_commit_send(data, data->ctx->ev, data->handle);
-    if (!req) {
-        error = ENOMEM;
+    subreq = sysdb_transaction_commit_send(tctx, tctx->ev, tctx->handle);
+    if (!subreq) {
+        ret = ENOMEM;
         goto fail;
     }
-    tevent_req_set_callback(req, groupdel_req_done, data);
-
+    tevent_req_set_callback(subreq, tools_transaction_done, tctx);
     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->ctx->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);
+    /* free transaction and signal error */
+    talloc_zfree(tctx->handle);
+    tctx->transaction_done = true;
+    tctx->error = ret;
 }
 
 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;
+    struct tools_ctx *tctx = NULL;
+    struct tevent_req *req;
 
     poptContext pc = NULL;
     struct poptOption long_options[] = {
@@ -150,7 +113,7 @@ int main(int argc, const char **argv)
 
     CHECK_ROOT(ret, debug_prg_name);
 
-    ret = init_sss_tools(&data);
+    ret = init_sss_tools(&tctx);
     if (ret != EOK) {
         DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
         ERROR("Error initializing the tools\n");
@@ -159,7 +122,7 @@ int main(int argc, const char **argv)
     }
 
     /* if the domain was not given as part of FQDN, default to local domain */
-    ret = get_domain(data, pc_groupname);
+    ret = parse_name_domain(tctx, pc_groupname);
     if (ret != EOK) {
         ERROR("Cannot get domain information\n");
         ret = EXIT_FAILURE;
@@ -167,34 +130,34 @@ int main(int argc, const char **argv)
     }
 
     /* arguments processed, go on to actual work */
-    grp_info = getgrnam(data->name);
+    grp_info = getgrnam(tctx->octx->name);
     if (grp_info) {
-        data->gid = grp_info->gr_gid;
+        tctx->octx->gid = grp_info->gr_gid;
     }
 
     /* arguments processed, go on to actual work */
-    if (id_in_range(data->gid, data->domain) != EOK) {
+    if (id_in_range(tctx->octx->uid, tctx->octx->domain) != EOK) {
         ERROR("The selected GID is outside the allowed range\n");
         ret = EXIT_FAILURE;
         goto fini;
     }
 
     /* groupdel */
-    req = sysdb_transaction_send(data, data->ctx->ev, data->ctx->sysdb);
+    req = sysdb_transaction_send(tctx->octx, tctx->ev, tctx->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);
+    tevent_req_set_callback(req, del_group_transaction, tctx);
 
-    while (!data->done) {
-        tevent_loop_once(data->ctx->ev);
+    while (!tctx->transaction_done) {
+        tevent_loop_once(tctx->ev);
     }
 
-    if (data->error) {
-        ret = data->error;
+    if (tctx->error) {
+        ret = tctx->error;
         DEBUG(1, ("sysdb operation failed (%d)[%s]\n", ret, strerror(ret)));
         switch (ret) {
             case ENOENT:
@@ -212,7 +175,7 @@ int main(int argc, const char **argv)
     ret = EXIT_SUCCESS;
 
 fini:
-    talloc_free(data);
+    talloc_free(tctx);
     poptFreeContext(pc);
     exit(ret);
 }
diff --git a/server/tools/sss_groupmod.c b/server/tools/sss_groupmod.c
index cc1a945..caf4466 100644
--- a/server/tools/sss_groupmod.c
+++ b/server/tools/sss_groupmod.c
@@ -31,219 +31,40 @@
 #include "db/sysdb.h"
 #include "tools/tools_util.h"
 
-static void mod_group_req_done(struct tevent_req *req)
+static void mod_group_transaction(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->ctx->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->ctx->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->ctx->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;
+    struct tools_ctx *tctx = tevent_req_callback_data(req,
+                                                struct tools_ctx);
+    struct tevent_req *subreq;
 
-    ret = sysdb_mod_group_member_recv(req);
+    ret = sysdb_transaction_recv(req, tctx, &tctx->handle);
     if (ret) {
-        return mod_group_done(data, ret);
+        tevent_req_error(req, ret);
+        return;
     }
     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->ctx->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);
+    /* groupmod */
+    ret = groupmod(tctx, tctx->ev,
+                  tctx->sysdb, tctx->handle, tctx->octx);
+    if (ret != EOK) {
+        goto fail;
     }
-    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);
+    subreq = sysdb_transaction_commit_send(tctx, tctx->ev, tctx->handle);
+    if (!subreq) {
+        ret = ENOMEM;
+        goto fail;
     }
+    tevent_req_set_callback(subreq, tools_transaction_done, tctx);
+    return;
 
-    return add_to_groups(data);
+fail:
+    /* free transaction and signal error */
+    talloc_zfree(tctx->handle);
+    tctx->transaction_done = true;
+    tctx->error = ret;
 }
 
 int main(int argc, const char **argv)
@@ -263,13 +84,11 @@ int main(int argc, const char **argv)
         POPT_TABLEEND
     };
     poptContext pc = NULL;
-    struct ops_ctx *data = NULL;
-    struct tevent_req *req;
+    struct tools_ctx *tctx = NULL;
     char *addgroups = NULL, *rmgroups = NULL;
     int ret;
-    struct group *grp_info;
-    gid_t old_gid = 0;
     const char *pc_groupname = NULL;
+    struct tevent_req *req;
 
     debug_prg_name = argv[0];
 
@@ -320,7 +139,7 @@ int main(int argc, const char **argv)
 
     CHECK_ROOT(ret, debug_prg_name);
 
-    ret = init_sss_tools(&data);
+    ret = init_sss_tools(&tctx);
     if (ret != EOK) {
         DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
         ERROR("Error initializing the tools\n");
@@ -328,19 +147,17 @@ int main(int argc, const char **argv)
         goto fini;
     }
 
-    ret = get_domain(data, pc_groupname);
+    ret = parse_name_domain(tctx, pc_groupname);
     if (ret != EOK) {
         ERROR("Cannot get domain information\n");
         ret = EXIT_FAILURE;
         goto fini;
     }
 
-    data->gid = pc_gid;
+    tctx->octx->gid = pc_gid;
 
     if (addgroups) {
-        ret = parse_groups(data,
-                addgroups,
-                &data->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");
@@ -349,9 +166,7 @@ int main(int argc, const char **argv)
     }
 
     if (rmgroups) {
-        ret = parse_groups(data,
-                rmgroups,
-                &data->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");
@@ -359,33 +174,27 @@ int main(int argc, const char **argv)
         }
     }
 
-    /* arguments processed, go on to actual work */
-    grp_info = getgrnam(data->name);
-    if (grp_info) {
-       old_gid = grp_info->gr_gid;
-    }
-
-    if (id_in_range(data->gid, data->domain) != EOK) {
+    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;
     }
 
-    req = sysdb_transaction_send(data, data->ctx->ev, data->ctx->sysdb);
+    req = sysdb_transaction_send(tctx->octx, tctx->ev, tctx->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);
+    tevent_req_set_callback(req, mod_group_transaction, tctx);
 
-    while (!data->done) {
-        tevent_loop_once(data->ctx->ev);
+    while (!tctx->transaction_done) {
+        tevent_loop_once(tctx->ev);
     }
 
-    if (data->error) {
-        ret = data->error;
+    if (tctx->error) {
+        ret = tctx->error;
         DEBUG(1, ("sysdb operation failed (%d)[%s]\n", ret, strerror(ret)));
         switch (ret) {
             case ENOENT:
@@ -411,6 +220,6 @@ fini:
     free(addgroups);
     free(rmgroups);
     poptFreeContext(pc);
-    talloc_free(data);
+    talloc_free(tctx);
     exit(ret);
 }
diff --git a/server/tools/sss_sync_ops.c b/server/tools/sss_sync_ops.c
new file mode 100644
index 0000000..d9db230
--- /dev/null
+++ b/server/tools/sss_sync_ops.c
@@ -0,0 +1,1467 @@
+/*
+    Authors:
+        Jakub Hrozek <jhrozek@redhat.com>
+
+    Copyright (C) 2009 Red Hat
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <tevent.h>
+#include <talloc.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+
+#include "tools/sss_sync_ops.h"
+
+/* Default settings for user attributes */
+#define CONFDB_DFL_SECTION "config/user_defaults"
+
+#define DFL_SHELL_ATTR     "defaultShell"
+#define DFL_BASEDIR_ATTR   "baseDirectory"
+
+#define DFL_SHELL_VAL      "/bin/bash"
+#define DFL_BASEDIR_VAL    "/home"
+
+#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 {
+    int error;
+    bool done;
+};
+
+/*
+ * Generic recv function
+ */
+static int sync_ops_recv(struct tevent_req *req)
+{
+    enum tevent_req_state tstate;
+    uint64_t err;
+
+    if (tevent_req_is_error(req, &tstate, &err)) {
+        return err;
+    }
+
+    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);
+    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);
+    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);
+}
+
+/*
+ * 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 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(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(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;
+    }
+
+    ret = EOK;
+done:
+    talloc_free(dfl_shell);
+    talloc_free(basedir);
+    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;
+}
+
diff --git a/server/tools/sss_sync_ops.h b/server/tools/sss_sync_ops.h
new file mode 100644
index 0000000..525f2ff
--- /dev/null
+++ b/server/tools/sss_sync_ops.h
@@ -0,0 +1,86 @@
+/*
+    Authors:
+        Jakub Hrozek <jhrozek@redhat.com>
+
+    Copyright (C) 2009 Red Hat
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __SSS_OPS_H__
+#define __SSS_OPS_H__
+
+#include <stdbool.h>
+
+#define DO_LOCK     1
+#define DO_UNLOCK   2
+
+struct ops_ctx {
+    struct sss_domain_info *domain;
+
+    char *name;
+    uid_t uid;
+    gid_t gid;
+    char *gecos;
+    char *home;
+    char *shell;
+    int   lock;
+
+    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);
+
+/* 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);
+
+#endif /* __SSS_OPS_H__ */
+
diff --git a/server/tools/sss_useradd.c b/server/tools/sss_useradd.c
index 2ce1607..becf205 100644
--- a/server/tools/sss_useradd.c
+++ b/server/tools/sss_useradd.c
@@ -43,27 +43,27 @@
 
 static void get_gid_callback(void *ptr, int error, struct ldb_result *res)
 {
-    struct ops_ctx *data = talloc_get_type(ptr, struct ops_ctx);
+    struct tools_ctx *tctx = talloc_get_type(ptr, struct tools_ctx);
 
     if (error) {
-        data->error = error;
+        tctx->error = error;
         return;
     }
 
     switch (res->count) {
     case 0:
-        data->error = ENOENT;
+        tctx->error = ENOENT;
         break;
 
     case 1:
-        data->gid = ldb_msg_find_attr_as_uint(res->msgs[0], SYSDB_GIDNUM, 0);
-        if (data->gid == 0) {
-            data->error = ERANGE;
+        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:
-        data->error = EFAULT;
+        tctx->error = EFAULT;
         break;
     }
 }
@@ -72,32 +72,32 @@ static void get_gid_callback(void *ptr, int error, struct ldb_result *res)
  * is given, returns that as integer (rationale: shadow-utils)
  * On error, returns -EINVAL
  */
-static int get_gid(struct ops_ctx *data, const char *groupname)
+static int get_gid(struct tools_ctx *tctx, const char *groupname)
 {
     char *end_ptr;
     int ret;
 
     errno = 0;
-    data->gid = strtoul(groupname, &end_ptr, 10);
+    tctx->octx->gid = strtoul(groupname, &end_ptr, 10);
     if (groupname == '\0' || *end_ptr != '\0' ||
-        errno != 0 || data->gid == 0) {
+        errno != 0 || tctx->octx->gid == 0) {
         /* Does not look like a gid - find the group name */
 
-        ret = sysdb_getgrnam(data, data->ctx->sysdb,
-                             data->domain, groupname,
-                             get_gid_callback, data);
+        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;
         }
 
-        data->error = EOK;
-        data->gid = 0;
-        while ((data->error == EOK) && (data->gid == 0)) {
-            tevent_loop_once(data->ctx->ev);
+        tctx->error = EOK;
+        tctx->octx->gid = 0;
+        while ((tctx->error == EOK) && (tctx->octx->gid == 0)) {
+            tevent_loop_once(tctx->ev);
         }
 
-        if (data->error) {
+        if (tctx->error) {
             DEBUG(1, ("sysdb_getgrnam failed: %d\n", ret));
             goto done;
         }
@@ -107,131 +107,40 @@ done:
     return ret;
 }
 
-static void add_user_req_done(struct tevent_req *req)
+static void add_user_transaction(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->ctx->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->ctx->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 tools_ctx *tctx = tevent_req_callback_data(req,
+                                                struct tools_ctx);
     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);
+    ret = sysdb_transaction_recv(req, tctx, &tctx->handle);
+    if (ret) {
+        tevent_req_error(req, ret);
+        return;
     }
+    talloc_zfree(req);
 
-    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);
+    /* useradd */
+    ret = useradd(tctx, tctx->ev,
+                  tctx->sysdb, tctx->handle, tctx->octx);
+    if (ret != EOK) {
+        goto fail;
     }
 
-    subreq = sysdb_mod_group_member_send(data, data->ctx->ev, data->handle,
-                                         member_dn, parent_dn,
-                                         LDB_FLAG_MOD_ADD);
+    subreq = sysdb_transaction_commit_send(tctx, tctx->ev, tctx->handle);
     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);
+        ret = ENOMEM;
+        goto fail;
     }
+    tevent_req_set_callback(subreq, tools_transaction_done, tctx);
+    return;
 
-    return add_to_groups(data);
+fail:
+    /* free transaction and signal error */
+    talloc_zfree(tctx->handle);
+    tctx->transaction_done = true;
+    tctx->error = ret;
 }
 
 int main(int argc, const char **argv)
@@ -241,7 +150,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[] = {
@@ -256,8 +164,8 @@ int main(int argc, const char **argv)
         POPT_TABLEEND
     };
     poptContext pc = NULL;
-    struct ops_ctx *data = NULL;
     struct tevent_req *req;
+    struct tools_ctx *tctx = NULL;
     char *groups = NULL;
     int ret;
 
@@ -302,7 +210,7 @@ int main(int argc, const char **argv)
 
     CHECK_ROOT(ret, debug_prg_name);
 
-    ret = init_sss_tools(&data);
+    ret = init_sss_tools(&tctx);
     if (ret != EOK) {
         DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
         ERROR("Error initializing the tools\n");
@@ -311,7 +219,7 @@ int main(int argc, const char **argv)
     }
 
     /* if the domain was not given as part of FQDN, default to local domain */
-    ret = get_domain(data, pc_username);
+    ret = parse_name_domain(tctx, pc_username);
     if (ret != EOK) {
         ERROR("Cannot get domain information\n");
         ret = EXIT_FAILURE;
@@ -319,7 +227,7 @@ int main(int argc, const char **argv)
     }
 
     if (groups) {
-        ret = parse_groups(data, groups, &data->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");
@@ -329,7 +237,7 @@ int main(int argc, const char **argv)
 
     /* Same as shadow-utils useradd, -g can specify gid or group name */
     if (pc_group != NULL) {
-        ret = get_gid(data, pc_group);
+        ret = get_gid(tctx, pc_group);
         if (ret != EOK) {
             ERROR("Cannot get group information for the user\n");
             ret = EXIT_FAILURE;
@@ -337,80 +245,42 @@ int main(int argc, const char **argv)
         }
     }
 
-    data->uid = pc_uid;
+    tctx->octx->uid = pc_uid;
 
     /*
      * 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(tctx, tctx->confdb, tctx->octx,
+                           pc_gecos, pc_home, pc_shell);
+    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(data->uid, data->domain) != EOK) {
+    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;
     }
 
     /* useradd */
-    req = sysdb_transaction_send(data, data->ctx->ev, data->ctx->sysdb);
+    req = sysdb_transaction_send(tctx->octx, tctx->ev, tctx->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);
+    tevent_req_set_callback(req, add_user_transaction, tctx);
 
-    while (!data->done) {
-        tevent_loop_once(data->ctx->ev);
+    while (!tctx->transaction_done) {
+        tevent_loop_once(tctx->ev);
     }
 
-    if (data->error) {
-        ret = data->error;
+    if (tctx->error) {
+        ret = tctx->error;
         switch (ret) {
             case EEXIST:
                 ERROR("A user with the same name or UID already exists\n");
@@ -430,7 +300,7 @@ int main(int argc, const char **argv)
 
 fini:
     poptFreeContext(pc);
-    talloc_free(data);
+    talloc_free(tctx);
     free(groups);
     exit(ret);
 }
diff --git a/server/tools/sss_userdel.c b/server/tools/sss_userdel.c
index ead6188..7c20a69 100644
--- a/server/tools/sss_userdel.c
+++ b/server/tools/sss_userdel.c
@@ -30,86 +30,47 @@
 #include "util/util.h"
 #include "tools/tools_util.h"
 
-static void userdel_req_done(struct tevent_req *req)
+static void del_user_transaction(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;
-}
+    int ret;
+    struct tools_ctx *tctx = tevent_req_callback_data(req,
+                                                struct tools_ctx);
+    struct tevent_req *subreq;
 
-/* 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;
+    ret = sysdb_transaction_recv(req, tctx, &tctx->handle);
+    if (ret) {
+        tevent_req_error(req, ret);
+        return;
+    }
+    talloc_zfree(req);
 
-    if (error != EOK) {
+    /* userdel */
+    ret = userdel(tctx, tctx->ev,
+                  tctx->sysdb, tctx->handle, tctx->octx);
+    if (ret != EOK) {
         goto fail;
     }
 
-    req = sysdb_transaction_commit_send(data, data->ctx->ev, data->handle);
-    if (!req) {
-        error = ENOMEM;
+    subreq = sysdb_transaction_commit_send(tctx, tctx->ev, tctx->handle);
+    if (!subreq) {
+        ret = ENOMEM;
         goto fail;
     }
-    tevent_req_set_callback(req, userdel_req_done, data);
-
+    tevent_req_set_callback(subreq, tools_transaction_done, tctx);
     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->ctx->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);
+    /* free transaction and signal error */
+    talloc_zfree(tctx->handle);
+    tctx->transaction_done = true;
+    tctx->error = ret;
 }
 
 int main(int argc, const char **argv)
 {
     int ret = EXIT_SUCCESS;
-    struct ops_ctx *data = NULL;
     struct tevent_req *req;
+    struct tools_ctx *tctx = NULL;
     struct passwd *pwd_info;
     const char *pc_username = NULL;
 
@@ -152,7 +113,7 @@ int main(int argc, const char **argv)
 
     CHECK_ROOT(ret, debug_prg_name);
 
-    ret = init_sss_tools(&data);
+    ret = init_sss_tools(&tctx);
     if (ret != EOK) {
         DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
         ERROR("Error initializing the tools\n");
@@ -161,7 +122,7 @@ int main(int argc, const char **argv)
     }
 
     /* if the domain was not given as part of FQDN, default to local domain */
-    ret = get_domain(data, pc_username);
+    ret = parse_name_domain(tctx, pc_username);
     if (ret != EOK) {
         ERROR("Cannot get domain information\n");
         ret = EXIT_FAILURE;
@@ -169,33 +130,33 @@ int main(int argc, const char **argv)
     }
 
     /* arguments processed, go on to actual work */
-    pwd_info = getpwnam(data->name);
+    pwd_info = getpwnam(tctx->octx->name);
     if (pwd_info) {
-        data->uid = pwd_info->pw_uid;
+        tctx->octx->uid = pwd_info->pw_uid;
     }
 
-    if (id_in_range(data->uid, data->domain) != EOK) {
+    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;
     }
 
     /* userdel */
-    req = sysdb_transaction_send(data, data->ctx->ev, data->ctx->sysdb);
+    req = sysdb_transaction_send(tctx->octx, tctx->ev, tctx->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);
+    tevent_req_set_callback(req, del_user_transaction, tctx);
 
-    while (!data->done) {
-        tevent_loop_once(data->ctx->ev);
+    while (!tctx->transaction_done) {
+        tevent_loop_once(tctx->ev);
     }
 
-    if (data->error) {
-        ret = data->error;
+    if (tctx->error) {
+        ret = tctx->error;
         DEBUG(1, ("sysdb operation failed (%d)[%s]\n", ret, strerror(ret)));
         switch (ret) {
             case ENOENT:
@@ -213,7 +174,7 @@ int main(int argc, const char **argv)
     ret = EXIT_SUCCESS;
 
 fini:
-    talloc_free(data);
+    talloc_free(tctx);
     poptFreeContext(pc);
     exit(ret);
 }
diff --git a/server/tools/sss_usermod.c b/server/tools/sss_usermod.c
index b410ed1..02ed74e 100644
--- a/server/tools/sss_usermod.c
+++ b/server/tools/sss_usermod.c
@@ -31,220 +31,40 @@
 #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)
-
-static void mod_user_req_done(struct tevent_req *req)
+static void mod_user_transaction(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->ctx->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->ctx->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->ctx->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;
+    struct tools_ctx *tctx = tevent_req_callback_data(req,
+                                                struct tools_ctx);
+    struct tevent_req *subreq;
 
-    ret = sysdb_mod_group_member_recv(req);
+    ret = sysdb_transaction_recv(req, tctx, &tctx->handle);
     if (ret) {
-        return mod_user_done(data, ret);
+        tevent_req_error(req, ret);
+        return;
     }
     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->ctx->ev,
-                                      data->handle,
-                                      member_dn,
-                                      parent_dn,
-                                      LDB_FLAG_MOD_ADD);
-    if (!req) {
-        return mod_user_done(data, ENOMEM);
+    /* usermod */
+    ret = usermod(tctx, tctx->ev,
+                  tctx->sysdb, tctx->handle, tctx->octx);
+    if (ret != EOK) {
+        goto fail;
     }
-    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);
+    subreq = sysdb_transaction_commit_send(tctx, tctx->ev, tctx->handle);
+    if (!subreq) {
+        ret = ENOMEM;
+        goto fail;
     }
+    tevent_req_set_callback(subreq, tools_transaction_done, tctx);
+    return;
 
-    return add_to_groups(data);
+fail:
+    /* free transaction and signal error */
+    talloc_zfree(tctx->handle);
+    tctx->transaction_done = true;
+    tctx->error = ret;
 }
 
 int main(int argc, const char **argv)
@@ -252,9 +72,9 @@ int main(int argc, const char **argv)
     int pc_lock = 0;
     uid_t pc_uid = 0;
     gid_t pc_gid = 0;
-    const char *pc_gecos = NULL;
-    const char *pc_home = NULL;
-    const char *pc_shell = NULL;
+    char *pc_gecos = NULL;
+    char *pc_home = NULL;
+    char *pc_shell = NULL;
     int pc_debug = 0;
     struct poptOption long_options[] = {
         POPT_AUTOHELP
@@ -271,13 +91,11 @@ int main(int argc, const char **argv)
         POPT_TABLEEND
     };
     poptContext pc = NULL;
-    struct ops_ctx *data = NULL;
-    struct tevent_req *req;
     char *addgroups = NULL, *rmgroups = NULL;
     int ret;
-    struct passwd *pwd_info;
-    uid_t old_uid = 0;
     const char *pc_username = NULL;
+    struct tools_ctx *tctx = NULL;
+    struct tevent_req *req;
 
     debug_prg_name = argv[0];
 
@@ -336,7 +154,7 @@ int main(int argc, const char **argv)
 
     CHECK_ROOT(ret, debug_prg_name);
 
-    ret = init_sss_tools(&data);
+    ret = init_sss_tools(&tctx);
     if (ret != EOK) {
         DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
         ERROR("Error initializing the tools\n");
@@ -344,36 +162,22 @@ int main(int argc, const char **argv)
         goto fini;
     }
 
-    data->attrs = sysdb_new_attrs(data->ctx);
-    if (data->attrs == NULL) {
-        DEBUG(1, ("Could not allocate memory for sysdb_attrs context\n"));
-        ERROR("Out of memory\n");
-        return ENOMEM;
-    }
-
     /* if the domain was not given as part of FQDN, default to local domain */
-    ret = get_domain(data, pc_username);
+    ret = parse_name_domain(tctx, pc_username);
     if (ret != EOK) {
         ERROR("Cannot get domain information\n");
         ret = EXIT_FAILURE;
         goto fini;
     }
 
-    pwd_info = getpwnam(data->name);
-    if (pwd_info) {
-        old_uid = pwd_info->pw_uid;
-    }
-
-    if (id_in_range(data->uid, data->domain) != EOK) {
+    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(data,
-                           addgroups,
-                           &data->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");
@@ -382,9 +186,7 @@ int main(int argc, const char **argv)
     }
 
     if (rmgroups) {
-        ret = parse_groups(data,
-                           rmgroups,
-                           &data->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");
@@ -392,85 +194,28 @@ 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");
-    }
+    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;
 
-    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->ctx->ev, data->ctx->sysdb);
+    req = sysdb_transaction_send(tctx->octx, tctx->ev, tctx->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, mod_user, data);
+    tevent_req_set_callback(req, mod_user_transaction, tctx);
 
-    while (!data->done) {
-        tevent_loop_once(data->ctx->ev);
+    while (!tctx->transaction_done) {
+        tevent_loop_once(tctx->ev);
     }
 
-    if (data->error) {
-        ret = data->error;
-
-        DEBUG(1, ("sysdb operation failed (%d)[%s]\n", ret, strerror(ret)));
+    if (tctx->error) {
+        ret = tctx->error;
         switch (ret) {
             case ENOENT:
                 ERROR("Could not modify user - check if group names are correct\n");
@@ -495,6 +240,6 @@ fini:
     free(addgroups);
     free(rmgroups);
     poptFreeContext(pc);
-    talloc_free(data);
+    talloc_free(tctx);
     exit(ret);
 }
diff --git a/server/tools/tools_util.c b/server/tools/tools_util.c
index 4efe648..eeec13c 100644
--- a/server/tools/tools_util.c
+++ b/server/tools/tools_util.c
@@ -29,25 +29,10 @@
 #include "db/sysdb.h"
 #include "tools/tools_util.h"
 
-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 setup_db(TALLOC_CTX *mem_ctx, struct tools_ctx **tools_ctx)
+static int setup_db(TALLOC_CTX *mem_ctx, struct tools_ctx **tools_ctx)
 {
     char *confdb_path;
     struct tools_ctx *ctx;
-    struct sss_domain_info *dom = NULL;
     int ret;
 
     ctx = talloc_zero(mem_ctx, struct tools_ctx);
@@ -78,14 +63,7 @@ int setup_db(TALLOC_CTX *mem_ctx, struct tools_ctx **tools_ctx)
         return ret;
     }
 
-    ret = confdb_get_domains(ctx->confdb, &ctx->domains);
-    if (ret != EOK) {
-        DEBUG(1, ("Could not get domains\n"));
-        talloc_free(ctx);
-        return ret;
-    }
-
-    ret = confdb_get_domain(ctx->confdb, "local", &dom);
+    ret = confdb_get_domain(ctx->confdb, "local", &ctx->local);
     if (ret != EOK) {
         DEBUG(1, ("Could not get 'local' domain\n"));
         talloc_free(ctx);
@@ -93,7 +71,7 @@ int setup_db(TALLOC_CTX *mem_ctx, struct tools_ctx **tools_ctx)
     }
 
     /* open 'local' sysdb at default path */
-    ret = sysdb_domain_init(ctx, ctx->ev, dom, DB_PATH, &ctx->sysdb);
+    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"));
         talloc_free(ctx);
@@ -157,32 +135,23 @@ int parse_groups(TALLOC_CTX *mem_ctx, const char *optstr, char ***_out)
     return EOK;
 }
 
-static int parse_name_domain(struct ops_ctx *octx,
-                             const char *fullname)
+int parse_name_domain(struct tools_ctx *tctx,
+                      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);
+    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", octx->name));
+    DEBUG(5, ("Parsed username: %s\n", tctx->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) {
+        /* only the local domain, whatever named is allowed in tools */
+        if (strcasecmp(domain, tctx->local->name) != 0) {
             DEBUG(0, ("Invalid domain %s specified in FQDN\n", domain));
             return EINVAL;
         }
@@ -191,25 +160,6 @@ static int parse_name_domain(struct ops_ctx *octx,
     return EOK;
 }
 
-int get_domain(struct ops_ctx *octx,
-               const char *fullname)
-{
-    int ret;
-
-    ret = parse_name_domain(octx, fullname);
-    if (ret != EOK) {
-        return ret;
-    }
-    if (octx->domain == NULL) {
-        octx->domain = get_local_domain(octx->ctx);
-        if (octx->domain == NULL) {
-            return EINVAL;
-        }
-    }
-
-    return EOK;
-}
-
 int id_in_range(uint32_t id,
                 struct sss_domain_info *dom)
 {
@@ -246,7 +196,7 @@ int set_locale(void)
     return EOK;
 }
 
-int init_sss_tools(struct ops_ctx **_octx)
+int init_sss_tools(struct tools_ctx **_tctx)
 {
     int ret;
     struct tools_ctx *tctx;
@@ -264,7 +214,6 @@ int init_sss_tools(struct ops_ctx **_octx)
     ret = setup_db(octx, &tctx);
     if (ret != EOK) {
         DEBUG(1, ("Could not set up database\n"));
-        ret = EXIT_FAILURE;
         goto fini;
     }
 
@@ -274,10 +223,24 @@ int init_sss_tools(struct ops_ctx **_octx)
         goto fini;
     }
 
-    octx->ctx = tctx;
-    *_octx = octx;
+    octx->domain = tctx->local;
+    tctx->octx = octx;
+
+    *_tctx = tctx;
     ret = EOK;
 fini:
     return ret;
 }
 
+/*
+ * Common transaction finish
+ */
+void tools_transaction_done(struct tevent_req *req)
+{
+    struct tools_ctx *tctx = tevent_req_callback_data(req,
+                                                      struct tools_ctx);
+
+    tctx->error = sysdb_transaction_commit_recv(req);
+    tctx->transaction_done = true;
+}
+
diff --git a/server/tools/tools_util.h b/server/tools/tools_util.h
index 2aca4b8..b6509c2 100644
--- a/server/tools/tools_util.h
+++ b/server/tools/tools_util.h
@@ -24,6 +24,7 @@
 #define __TOOLS_UTIL_H__
 
 #include "util/sssd-i18n.h"
+#include "tools/sss_sync_ops.h"
 
 #define CHECK_ROOT(val, prg_name) do { \
     val = getuid(); \
@@ -39,41 +40,26 @@ 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 sss_domain_info *domain;
 
-    char *name;
-    uid_t uid;
-    gid_t gid;
-    char *gecos;
-    char *home;
-    char *shell;
-    struct sysdb_attrs *attrs;
+    struct sss_names_ctx *snctx;
+    struct sss_domain_info *local;
 
-    char **addgroups;
-    char **rmgroups;
-    char **groups;
-    int cur;
+    struct ops_ctx *octx;
 
     struct sysdb_handle *handle;
+    int transaction_done;
     int error;
-    bool done;
 };
 
-int init_sss_tools(struct ops_ctx **_octx);
+int init_sss_tools(struct tools_ctx **_tctx);
 
 void usage(poptContext pc, const char *error);
 
 int set_locale(void);
 
-int get_domain(struct ops_ctx *octx,
-               const char *fullname);
+
+int parse_name_domain(struct tools_ctx *tctx,
+                      const char *fullname);
 
 int id_in_range(uint32_t id,
                 struct sss_domain_info *dom);
@@ -82,4 +68,6 @@ int parse_groups(TALLOC_CTX *mem_ctx,
                  const char *optstr,
                  char ***_out);
 
+void tools_transaction_done(struct tevent_req *req);
+
 #endif  /* __TOOLS_UTIL_H__ */
-- 
1.6.2.5

