From aff9a608492defdf3947c20a91ccbc273bab8112 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Mon, 1 Feb 2010 11:39:51 +0100
Subject: [PATCH] sss_groupshow improvements

This patch fixes the following issues:
    * man page issues (#395)
    * show info on MPGs (#396)
    * recursively print indirect members (#397)
    * the list of groupnames is comma separated (#398)
    * output inconsistencies (#399)
---
 server/man/sss_groupshow.8.xml |   12 +-
 server/tools/sss_groupshow.c   |  767 +++++++++++++++++++++++++++++++++++-----
 2 files changed, 680 insertions(+), 99 deletions(-)

diff --git a/server/man/sss_groupshow.8.xml b/server/man/sss_groupshow.8.xml
index 816e2b7..13e2dfc 100644
--- a/server/man/sss_groupshow.8.xml
+++ b/server/man/sss_groupshow.8.xml
@@ -31,13 +31,23 @@
         <para>
             <command>sss_groupshow</command> displays information about a group
             identified by its name <replaceable>GROUP</replaceable>. The information
-            include the group ID number, members of the group and the parent group.
+            includes the group ID number, members of the group and the parent group.
         </para>
     </refsect1>
 
     <refsect1 id='options'>
         <title>OPTIONS</title>
         <variablelist remap='IP'>
+            <varlistentry>
+                <term>
+                    <option>-R</option>,<option>--recursive</option>
+                </term>
+                <listitem>
+                    <para>
+                        Also print indirect group members in a tree-like hierarchy.
+                    </para>
+                </listitem>
+            </varlistentry>
             <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/param_help.xml" />
         </variablelist>
     </refsect1>
diff --git a/server/tools/sss_groupshow.c b/server/tools/sss_groupshow.c
index b942c4e..74855db 100644
--- a/server/tools/sss_groupshow.c
+++ b/server/tools/sss_groupshow.c
@@ -29,19 +29,25 @@
 #include "tools/tools_util.h"
 #include "tools/sss_sync_ops.h"
 
-struct group_show_state {
-    const char **user_members;
-    const char **group_members;
-    const char **memberofs;
+#define PADDING_SPACES   4
+#define GROUP_SHOW_ATTRS { SYSDB_MEMBEROF, SYSDB_GIDNUM, \
+                           SYSDB_MEMBER, SYSDB_NAME, \
+                           NULL }
+#define GROUP_SHOW_MPG_ATTRS { SYSDB_MEMBEROF, SYSDB_UIDNUM, \
+                                SYSDB_NAME, NULL }
+
+struct group_info {
+    const char *name;
     gid_t gid;
+    bool  mpg;
 
-    struct sysdb_ctx *sysdb;
-    struct sss_domain_info *domain;
+    const char **user_members;
+    const char **memberofs;
 
-    bool done;
-    int  ret;
+    struct group_info **group_members;
 };
 
+/*==================Helper routines to process results================= */
 const char *rdn_as_string(TALLOC_CTX *mem_ctx,
                           struct ldb_dn *dn)
 {
@@ -55,12 +61,40 @@ const char *rdn_as_string(TALLOC_CTX *mem_ctx,
     return ldb_dn_escape_value(mem_ctx, *val);;
 }
 
-int parse_members(TALLOC_CTX *mem_ctx,
-                  struct ldb_context *ldb,
-                  struct sss_domain_info *domain,
-                  struct ldb_message_element *el,
-                  const char ***user_members,
-                  const char ***group_members)
+static int parse_memberofs(struct ldb_context *ldb,
+                           struct ldb_message_element *el,
+                           struct group_info *gi)
+{
+    int i;
+    struct ldb_dn *dn = NULL;
+
+    gi->memberofs = talloc_array(gi, const char *, el->num_values+1);
+    if (gi->memberofs == NULL) {
+        return ENOMEM;
+    }
+
+    for (i = 0; i< el->num_values; ++i) {
+        dn = ldb_dn_from_ldb_val(gi, ldb, &(el->values[i]));
+        gi->memberofs[i] = talloc_strdup(gi, rdn_as_string(gi, dn));
+        talloc_zfree(dn);
+        if (gi->memberofs[i] == NULL) {
+            return ENOMEM;
+        }
+        DEBUG(6, ("memberof value: %s\n", gi->memberofs[i]));
+    }
+    gi->memberofs[el->num_values] = NULL;
+
+    return EOK;
+}
+
+static int parse_members(TALLOC_CTX *mem_ctx,
+                         struct ldb_context *ldb,
+                         struct sss_domain_info *domain,
+                         struct ldb_message_element *el,
+                         const  char *parent_name,
+                         const  char ***user_members,
+                         const  char ***group_members,
+                         int    *num_group_members)
 {
     struct ldb_dn *user_basedn = NULL, *group_basedn = NULL;
     struct ldb_dn *parent_dn = NULL;
@@ -114,6 +148,11 @@ int parse_members(TALLOC_CTX *mem_ctx,
                 ret = ENOMEM;
                 goto fail;
             }
+            if (parent_name && strcmp(gm[gm_index], parent_name) == 0) {
+                DEBUG(6, ("Skipping circular nesting for group %s\n",
+                          gm[gm_index]));
+                continue;
+            }
             DEBUG(6, ("Group member %s\n", gm[gm_index]));
             gm_index++;
         } else {
@@ -123,8 +162,8 @@ int parse_members(TALLOC_CTX *mem_ctx,
             goto fail;
         }
 
-        talloc_free(dn);
-        talloc_free(parent_dn);
+        talloc_zfree(dn);
+        talloc_zfree(parent_dn);
     }
     um[um_index] = NULL;
     gm[gm_index] = NULL;
@@ -138,43 +177,58 @@ int parse_members(TALLOC_CTX *mem_ctx,
 
     *user_members = um;
     *group_members = gm;
-    talloc_free(tmp_ctx);
+    *num_group_members = gm_index;
+    talloc_zfree(tmp_ctx);
     return EOK;
 
 fail:
-    talloc_free(um);
-    talloc_free(gm);
-    talloc_free(tmp_ctx);
+    talloc_zfree(um);
+    talloc_zfree(gm);
+    talloc_zfree(tmp_ctx);
     return ret;
 }
 
-static void group_show_done(struct tevent_req *req)
+static int process_group(TALLOC_CTX *mem_ctx,
+                         struct ldb_context *ldb,
+                         struct ldb_message *msg,
+                         struct sss_domain_info *domain,
+                         const  char *parent_name,
+                         struct group_info **info,
+                         const char ***group_members,
+                         int    *num_group_members)
 {
-    struct group_show_state *state = tevent_req_callback_data(req,
-                                                     struct group_show_state);
-    int ret;
-    int i;
-    struct ldb_message *msg = NULL;
-    struct ldb_context *ldb = sysdb_ctx_get_ldb(state->sysdb);
     struct ldb_message_element *el;
-    struct ldb_dn *dn = NULL;
+    int ret;
+    struct group_info *gi = NULL;
 
-    ret = sysdb_search_group_recv(req, state, &msg);
-    talloc_zfree(req);
-    if (ret) {
-        DEBUG(2, ("Search failed: %s (%d)\n", strerror(ret), ret));
-        ret = EIO;
+    DEBUG(6, ("Found entry %s\n", ldb_dn_get_linearized(msg->dn)));
+
+    gi = talloc_zero(mem_ctx, struct group_info);
+    if (!gi) {
+        ret = ENOMEM;
         goto done;
     }
 
-    DEBUG(6, ("Found entry %s\n", ldb_dn_get_linearized(msg->dn)));
+    /* mandatory data - name and gid */
+    gi->name = talloc_strdup(gi,
+                             ldb_msg_find_attr_as_string(msg,
+                                                         SYSDB_NAME,
+                                                         NULL));
+    gi->gid = ldb_msg_find_attr_as_uint64(msg,
+                                          SYSDB_GIDNUM, 0);
+    if (gi->gid == 0 || gi->name == NULL) {
+        DEBUG(3, ("No name or no GID?\n"));
+        ret = EIO;
+        goto done;
+    }
 
     /* list members */
     el = ldb_msg_find_element(msg, SYSDB_MEMBER);
     if (el) {
-        ret = parse_members(state, ldb, state->domain, el,
-                            &state->user_members,
-                            &state->group_members);
+        ret = parse_members(gi, ldb, domain, el,
+                            parent_name,
+                            &gi->user_members,
+                            group_members, num_group_members);
         if (ret != EOK) {
             goto done;
         }
@@ -183,49 +237,551 @@ static void group_show_done(struct tevent_req *req)
     /* list memberofs */
     el = ldb_msg_find_element(msg, SYSDB_MEMBEROF);
     if (el) {
-        state->memberofs = talloc_array(state, const char *, el->num_values+1);
-        if (state->memberofs == NULL) {
-            ret = ENOMEM;
+        ret = parse_memberofs(ldb, el, gi);
+        if (ret != EOK) {
             goto done;
         }
+    }
 
-        for (i = 0; i< el->num_values; ++i) {
-            dn = ldb_dn_from_ldb_val(state, ldb, &(el->values[i]));
-            state->memberofs[i] = rdn_as_string(state, dn);
-            if (state->memberofs[i] == NULL) {
-                ret = ENOMEM;
-                goto done;
+    *info = gi;
+    return EOK;
+done:
+    talloc_zfree(gi);
+    return ret;
+}
+
+/*========Find info about a group and recursively about subgroups====== */
+struct group_show_state {
+    struct tevent_context *ev;
+    struct sysdb_ctx *sysdb;
+    struct sysdb_handle *handle;
+    struct sss_domain_info *domain;
+
+    struct group_info *root;
+    bool recursive;
+};
+
+static void group_show_root_done(struct tevent_req *subreq);
+static void group_show_recurse_done(struct tevent_req *subreq);
+
+struct tevent_req *group_show_recurse_send(TALLOC_CTX *,
+                                           struct group_show_state *,
+                                           struct group_info *,
+                                           const char **,
+                                           const int  );
+static int group_show_recurse_recv(TALLOC_CTX *, struct tevent_req *,
+                                   struct group_info ***);
+
+struct tevent_req *group_show_send(TALLOC_CTX *mem_ctx,
+                                   struct tevent_context *ev,
+                                   struct sysdb_ctx *sysdb,
+                                   struct sysdb_handle *handle,
+                                   struct sss_domain_info *domain,
+                                   bool   recursive,
+                                   const char *name)
+{
+    struct group_show_state *search_state = NULL;
+    struct tevent_req *subreq = NULL;
+    struct tevent_req *req = NULL;
+    static const char *attrs[] = GROUP_SHOW_ATTRS;
+
+    req = tevent_req_create(mem_ctx, &search_state, struct group_show_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    search_state->ev = ev;
+    search_state->sysdb = sysdb;
+    search_state->handle = handle;
+    search_state->domain = domain;
+    search_state->recursive = recursive;
+
+    /* First, search for the root group */
+    subreq = sysdb_search_group_by_name_send(search_state,
+                                             search_state->ev,
+                                             search_state->sysdb,
+                                             search_state->handle,
+                                             search_state->domain,
+                                             name, attrs);
+    if (!subreq) {
+        talloc_zfree(req);
+        return NULL;
+    }
+    tevent_req_set_callback(subreq, group_show_root_done, req);
+
+    return req;
+}
+
+static void group_show_root_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct group_show_state *state = tevent_req_data(req,
+                                                     struct group_show_state);
+    int ret;
+    int i;
+    struct ldb_message *msg = NULL;
+    const char **group_members = NULL;
+    int nmembers = 0;
+
+    ret = sysdb_search_group_recv(subreq, state, &msg);
+    talloc_zfree(subreq);
+    if (ret) {
+        DEBUG(2, ("Search failed: %s (%d)\n", strerror(ret), ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    ret = process_group(state,
+                        sysdb_ctx_get_ldb(state->sysdb),
+                        msg, state->domain, NULL, &state->root,
+                        &group_members, &nmembers);
+    if (ret != EOK) {
+        DEBUG(2, ("Group processing failed: %s (%d)\n",
+                   strerror(ret), ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (group_members == NULL) {
+        tevent_req_done(req);
+        return;
+    }
+
+    if (state->recursive == false) {
+        /* if not recursive, just fill in names */
+        state->root->group_members = talloc_array(state->root,
+                                                  struct group_info *,
+                                                  nmembers+1);
+        for (i=0; group_members[i]; i++) {
+            state->root->group_members[i] = talloc_zero(state->root,
+                                                        struct group_info);
+            if (!state->root->group_members) {
+                tevent_req_error(req, ENOMEM);
             }
-            DEBUG(6, ("memberof value: %s\n", state->memberofs[i]));
+            state->root->group_members[i]->name = talloc_strdup(state->root,
+                                                             group_members[i]);
+            if (!state->root->group_members[i]->name) {
+                tevent_req_error(req, ENOMEM);
+            }
+        }
+        state->root->group_members[nmembers] = NULL;
+
+        tevent_req_done(req);
+        return;
+    }
+
+    subreq = group_show_recurse_send(state->root, state,
+                                     state->root,
+                                     group_members,
+                                     nmembers);
+    if (!subreq) {
+        tevent_req_error(req, ret);
+        return;
+    }
+    tevent_req_set_callback(subreq, group_show_recurse_done, req);
+}
+
+static void group_show_recurse_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct group_show_state *state = tevent_req_data(req,
+                                                     struct group_show_state);
+    int ret;
+
+    ret = group_show_recurse_recv(state->root,
+                                  subreq,
+                                  &state->root->group_members);
+    talloc_zfree(subreq);
+    if (ret) {
+        DEBUG(2, ("Recursive search failed: %s (%d)\n", strerror(ret), ret));
+        tevent_req_error(req, EIO);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+static int group_show_recv(TALLOC_CTX *mem_ctx,
+                           struct tevent_req *req,
+                           struct group_info **res)
+{
+    struct group_show_state *state = tevent_req_data(req,
+                                                     struct group_show_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *res = talloc_move(mem_ctx, &state->root);
+
+    return EOK;
+}
+
+/*==================Recursive search for nested groups================= */
+struct group_show_recurse {
+    const char **names;
+    int  current;
+
+    struct group_info *parent;
+    struct group_show_state *state;
+
+    struct group_info **groups;
+};
+
+static int  group_show_recurse_search(struct tevent_req *,
+                                      struct group_show_recurse *);
+static void group_show_recurse_next(struct tevent_req *);
+static void group_show_recurse_level_done(struct tevent_req *);
+static void group_show_recurse_cont(struct tevent_req *);
+
+struct tevent_req *group_show_recurse_send(TALLOC_CTX *mem_ctx,
+                                           struct group_show_state *state,
+                                           struct group_info *parent,
+                                           const char **group_members,
+                                           const int  nmembers)
+{
+    struct tevent_req *req = NULL;
+    struct group_show_recurse *recurse_state = NULL;
+
+    req = tevent_req_create(mem_ctx, &recurse_state, struct group_show_recurse);
+    if (req == NULL) {
+        return NULL;
+    }
+    recurse_state->current = 0;
+    recurse_state->parent  = parent;
+    recurse_state->names   = group_members;
+    recurse_state->state   = state;
+    recurse_state->groups  = talloc_array(state->root,
+                                          struct group_info *,
+                                          nmembers+1); /* trailing NULL */
+
+    if (!recurse_state->names ||
+        !recurse_state->names[recurse_state->current]) {
+        talloc_zfree(req);
+        return NULL;
+    }
+
+    if (group_show_recurse_search(req, recurse_state) != EOK) {
+        talloc_zfree(req);
+        return NULL;
+    }
+
+    return req;
+}
+
+static int group_show_recurse_search(struct tevent_req *req,
+                                     struct group_show_recurse *recurse_state)
+{
+    static const char *attrs[] = GROUP_SHOW_ATTRS;
+    struct tevent_req *subreq = NULL;
+
+    /* Skip circular groups */
+    if (strcmp(recurse_state->names[recurse_state->current],
+               recurse_state->parent->name) == 0) {
+        DEBUG(0, ("CIRCULAR DEP DETECTED\n"));
+        group_show_recurse_cont(req);
+        return EOK;
+    }
+
+    subreq = sysdb_search_group_by_name_send(recurse_state->state,
+                                             recurse_state->state->ev,
+                                             recurse_state->state->sysdb,
+                                             recurse_state->state->handle,
+                                             recurse_state->state->domain,
+                                             recurse_state->names[recurse_state->current],
+                                             attrs);
+    if (!subreq) {
+        return ENOMEM;
+    }
+    tevent_req_set_callback(subreq, group_show_recurse_next, req);
+
+    return EOK;
+}
+
+static void group_show_recurse_next(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct group_show_recurse *recurse_state = tevent_req_data(req,
+                                                      struct group_show_recurse);
+    const char **group_members = NULL;
+    int nmembers = 0;
+    struct ldb_message *msg = NULL;
+    int ret;
+    struct tevent_req *recurse_req = NULL;
+
+    ret = sysdb_search_group_recv(subreq, recurse_state, &msg);
+    talloc_zfree(subreq);
+    if (ret) {
+        DEBUG(2, ("Search failed: %s (%d)\n", strerror(ret), ret));
+        tevent_req_error(req, EIO);
+        return;
+    }
+
+    ret = process_group(recurse_state->state->root,
+                        sysdb_ctx_get_ldb(recurse_state->state->sysdb),
+                        msg,
+                        recurse_state->state->domain,
+                        recurse_state->parent->name,
+                        &recurse_state->groups[recurse_state->current],
+                        &group_members,
+                        &nmembers);
+    if (ret != EOK) {
+        DEBUG(2, ("Group processing failed: %s (%d)\n",
+                   strerror(ret), ret));
+        tevent_req_error(req, ret);
+        return;
+    }
 
-            talloc_free(dn);
+    /* descend to another level */
+    if (nmembers > 0) {
+        recurse_req = group_show_recurse_send(recurse_state,
+                                        recurse_state->state,
+                                        recurse_state->groups[recurse_state->current],
+                                        group_members, nmembers);
+        if (!recurse_req) {
+            tevent_req_error(req, ENOMEM);
+            return;
         }
-        state->memberofs[el->num_values] = NULL;
+        /* to free group_members in the callback */
+        group_members = talloc_move(recurse_req, &group_members);
+        tevent_req_set_callback(recurse_req, group_show_recurse_level_done, req);
+        return;
     }
 
-    state->gid = ldb_msg_find_attr_as_uint64(msg,
-                                             SYSDB_GIDNUM, 0);
-    if (state->gid == 0) {
-        ret = EIO;
-        goto done;
+    /* Move to next group in the same level */
+    group_show_recurse_cont(req);
+}
+
+static void group_show_recurse_level_done(struct tevent_req *recurse_req)
+{
+    int ret;
+    struct tevent_req *req = tevent_req_callback_data(recurse_req,
+                                                      struct tevent_req);
+    struct group_show_recurse *recurse_state = tevent_req_data(recurse_req,
+                                                      struct group_show_recurse);
+
+    ret = group_show_recurse_recv(recurse_state->state->root, recurse_req,
+                                  &recurse_state->parent->group_members);
+    talloc_zfree(recurse_req);
+    if (ret) {
+        DEBUG(2, ("Recursive search failed: %s (%d)\n", strerror(ret), ret));
+        tevent_req_error(req, EIO);
+        return;
     }
 
-    ret = EOK;
-done:
-    state->ret = ret;
-    state->done = true;
+    /* Move to next group on the upper level */
+    group_show_recurse_cont(req);
+}
+
+static void group_show_recurse_cont(struct tevent_req *req)
+{
+    struct group_show_recurse *recurse_state = tevent_req_data(req,
+                                                      struct group_show_recurse);
+    int ret;
+
+    recurse_state->current++;
+    if (recurse_state->names[recurse_state->current] == NULL) {
+        recurse_state->groups[recurse_state->current] = NULL; /* Sentinel */
+        tevent_req_done(req);
+        return;
+    }
+
+    /* examine next group on the same level */
+    ret = group_show_recurse_search(req, recurse_state);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+}
+
+static int group_show_recurse_recv(TALLOC_CTX *mem_ctx,
+                                   struct tevent_req *req,
+                                   struct group_info ***out)
+{
+    struct group_show_recurse *recurse_state = tevent_req_data(req,
+                                                         struct group_show_recurse);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *out = talloc_move(mem_ctx, &recurse_state->groups);
+
+    return EOK;
+}
+
+/*==================Get info about MPG================================= */
+struct group_show_mpg_state {
+    struct ldb_context *ldb;
+    struct group_info *info;
+};
+
+static void group_show_mpg_done(struct tevent_req *);
+
+struct tevent_req *group_show_mpg_send(TALLOC_CTX *mem_ctx,
+                                       struct tevent_context *ev,
+                                       struct sysdb_ctx *sysdb,
+                                       struct sysdb_handle *handle,
+                                       struct sss_domain_info *domain,
+                                       const char *name)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct group_show_mpg_state *state;
+    static const char *mpg_attrs[] = GROUP_SHOW_MPG_ATTRS;
+
+    req = tevent_req_create(mem_ctx, &state, struct group_show_mpg_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->ldb = sysdb_ctx_get_ldb(sysdb);
+
+    subreq = sysdb_search_user_by_name_send(mem_ctx, ev, sysdb, handle,
+                                            domain, name, mpg_attrs);
+    if (!subreq) {
+        talloc_zfree(req);
+        return NULL;
+    }
+    tevent_req_set_callback(subreq, group_show_mpg_done, req);
+
+    return req;
+}
+
+static void group_show_mpg_done(struct tevent_req *subreq)
+{
+    int ret;
+    struct ldb_message *msg = NULL;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct group_show_mpg_state *state = tevent_req_data(req,
+                                                      struct group_show_mpg_state);
+
+    ret = sysdb_search_user_recv(subreq, req, &msg);
+    talloc_zfree(subreq);
+    if (ret) {
+        DEBUG(2, ("Search failed: %s (%d)\n", strerror(ret), ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    state->info = talloc_zero(state, struct group_info);
+    if (!state->info) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+
+    state->info->name = talloc_strdup(state->info,
+                                      ldb_msg_find_attr_as_string(msg,
+                                                                  SYSDB_NAME,
+                                                                  NULL));
+    state->info->gid = ldb_msg_find_attr_as_uint64(msg, SYSDB_UIDNUM, 0);
+    if (state->info->gid == 0 || state->info->name == NULL) {
+        DEBUG(3, ("No name or no GID?\n"));
+        tevent_req_error(req, EIO);
+        return;
+    }
+    state->info->mpg = true;
+
+    tevent_req_done(req);
+}
+
+static int group_show_mpg_recv(TALLOC_CTX *mem_ctx,
+                               struct tevent_req *req,
+                               struct group_info **res)
+{
+    struct group_show_mpg_state *state = tevent_req_data(req,
+                                                      struct group_show_mpg_state);
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    *res = talloc_move(mem_ctx, &state->info);
+
+    return EOK;
+}
+
+/*==================The main program=================================== */
+struct sss_groupshow_state {
+    struct group_info *root;
+
+    int ret;
+    bool done;
+};
+
+static void sss_group_show_done(struct tevent_req *req)
+{
+    int ret;
+    struct sss_groupshow_state *sss_state = tevent_req_callback_data(req,
+                                                   struct sss_groupshow_state);
+
+    ret = group_show_recv(sss_state, req, &sss_state->root);
+    talloc_zfree(req);
+
+    sss_state->ret = ret;
+    sss_state->done = true;
+}
+
+static void sss_group_show_mpg_done(struct tevent_req *req)
+{
+    int ret;
+    struct sss_groupshow_state *sss_state = tevent_req_callback_data(req,
+                                                   struct sss_groupshow_state);
+
+    ret = group_show_mpg_recv(sss_state, req, &sss_state->root);
+    talloc_zfree(req);
+
+    sss_state->ret = ret;
+    sss_state->done = true;
+}
+
+static void print_group_info(struct group_info *g, int level)
+{
+    int i;
+    char padding[512];
+    char fmt[8];
+
+    snprintf(fmt, 8, "%%%ds", level*PADDING_SPACES);
+    snprintf(padding, 512, fmt, "");
+
+    printf(_("%s%sGroup: %s\n"), padding,
+                                 g->mpg ? _("Magic Private ") : "",
+                                 g->name);
+    printf(_("%sGID number: %d\n"), padding, g->gid);
+
+    printf(_("%sMember users: "), padding);
+    if (g->user_members) {
+        for (i=0; g->user_members[i]; ++i) {
+            printf("%s:", g->user_members[i]);
+        }
+    }
+    printf(_("\n%sIs a member of: "), padding);
+    if (g->memberofs) {
+        for (i=0; g->memberofs[i]; ++i) {
+            printf("%s:", g->memberofs[i]);
+        }
+    }
+    printf(_("\n%sMember groups: "), padding);
+}
+
+static void print_recursive(struct group_info **group_members, int level)
+{
+    int i;
+
+    if (group_members == NULL) {
+        return;
+    }
+
+    level++;
+    for (i=0; group_members[i]; ++i) {
+        printf("\n");
+        print_group_info(group_members[i], level);
+        printf("\n");
+        print_recursive(group_members[i]->group_members, level);
+    }
 }
 
 int main(int argc, const char **argv)
 {
     int ret = EXIT_SUCCESS;
     int pc_debug = 0;
+    bool pc_recursive = false;
     const char *pc_groupname = NULL;
     struct tools_ctx *tctx = NULL;
     struct tevent_req *req = NULL;
-    const char *attrs[] = { SYSDB_MEMBEROF, SYSDB_GIDNUM,
-                            SYSDB_MEMBER, NULL };
-    struct group_show_state *search_state = NULL;
+    struct sss_groupshow_state *state = NULL;
     int i;
 
     poptContext pc = NULL;
@@ -233,6 +789,8 @@ int main(int argc, const char **argv)
         POPT_AUTOHELP
         { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug,
                     0, _("The debug level to run with"), NULL },
+        { "recursive", 'R', POPT_ARG_NONE, NULL, 'r',
+            _("Print indirect group members recursively"), NULL },
         POPT_TABLEEND
     };
 
@@ -249,14 +807,22 @@ int main(int argc, const char **argv)
     /* parse ops_ctx */
     pc = poptGetContext(NULL, argc, argv, long_options, 0);
     poptSetOtherOptionHelp(pc, "GROUPNAME");
-    if ((ret = poptGetNextOpt(pc)) < -1) {
+    while ((ret = poptGetNextOpt(pc)) > 0) {
+        switch (ret) {
+            case 'r':
+                pc_recursive = true;
+                break;
+        }
+    }
+
+    debug_level = pc_debug;
+
+    if (ret != -1) {
         usage(pc, poptStrerror(ret));
         ret = EXIT_FAILURE;
         goto fini;
     }
 
-    debug_level = pc_debug;
-
     pc_groupname = poptGetArg(pc);
     if (pc_groupname == NULL) {
         usage(pc, _("Specify group to show\n"));
@@ -286,31 +852,45 @@ int main(int argc, const char **argv)
         goto fini;
     }
 
-    /* Do the search */
-    search_state = talloc_zero(tctx, struct group_show_state);
-    if (search_state == NULL) {
-        ret = EXIT_FAILURE;
+    /* The search itself */
+    state = talloc_zero(tctx, struct sss_groupshow_state);
+    if (!state) {
         goto fini;
     }
-    search_state->sysdb = tctx->sysdb;
-    search_state->domain = tctx->local;
 
-    req = sysdb_search_group_by_name_send(tctx, tctx->ev, tctx->sysdb,
-                                          tctx->handle, tctx->local,
-                                          tctx->octx->name, attrs);
-    if (!req || !search_state) {
+    req = group_show_send(tctx, tctx->ev, tctx->sysdb, tctx->handle,
+                          tctx->local, pc_recursive, tctx->octx->name);
+    if (!req) {
+        ERROR("Cannot initiate search\n");
         ret = EXIT_FAILURE;
         goto fini;
     }
-    tevent_req_set_callback(req, group_show_done, search_state);
-
-    /* Busy wait */
-    while (!search_state->done) {
+    tevent_req_set_callback(req, sss_group_show_done, state);
+    while (!state->done) {
         tevent_loop_once(tctx->ev);
     }
+    ret = state->ret;
+
+    /* Also show MPGs */
+    if (ret == ENOENT) {
+        state->done = false;
+        state->ret  = EOK;
+
+        req = group_show_mpg_send(tctx, tctx->ev, tctx->sysdb, tctx->handle,
+                                  tctx->local, tctx->octx->name);
+        if (!req) {
+            ERROR("Cannot initiate search\n");
+            ret = EXIT_FAILURE;
+            goto fini;
+        }
+        tevent_req_set_callback(req, sss_group_show_mpg_done, state);
+        while (!state->done) {
+            tevent_loop_once(tctx->ev);
+        }
+        ret = state->ret;
+    }
 
     /* Process result */
-    ret = search_state->ret;
     if (ret) {
         DEBUG(1, ("sysdb operation failed (%d)[%s]\n", ret, strerror(ret)));
         switch (ret) {
@@ -328,27 +908,18 @@ int main(int argc, const char **argv)
     }
 
     /* print the results */
-    printf(_("Group: %s\nGID number: %d"), tctx->octx->name,
-                                           search_state->gid);
-    if (search_state->user_members) {
-        printf(_("\nMember users: "));
-        for (i=0; search_state->user_members[i]; ++i) {
-            printf("%s ", search_state->user_members[i]);
-        }
-    }
-    if (search_state->group_members) {
-        printf(_("\nMember groups: "));
-        for (i=0; search_state->group_members[i]; ++i) {
-            printf("%s ", search_state->group_members[i]);
-        }
-    }
-    if (search_state->memberofs) {
-        printf(_("\nIs a member of: "));
-        for (i=0; search_state->memberofs[i]; ++i) {
-            printf("%s ", search_state->memberofs[i]);
+    print_group_info(state->root, 0);
+    if (pc_recursive) {
+        printf("\n");
+        print_recursive(state->root->group_members, 0);
+    } else {
+        if (state->root->group_members) {
+            for (i=0; state->root->group_members[i]; ++i) {
+                printf("%s:", state->root->group_members[i]->name);
+            }
         }
+        printf("\n");
     }
-    printf("\n");
 
 fini:
     talloc_free(tctx);
-- 
1.6.6

