/* SSSD sss_groupshow Copyright (C) Jakub Hrozek 2010 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include "db/sysdb.h" #include "util/util.h" #include "tools/tools_util.h" #include "tools/sss_sync_ops.h" #define PADDING_SPACES 4 #define GROUP_SHOW_ATTRS { SYSDB_MEMBEROF, SYSDB_GIDNUM, \ SYSDB_MEMBER, SYSDB_NAME, \ NULL } #define GROUP_SHOW_MPG_ATTRS { SYSDB_MEMBEROF, SYSDB_UIDNUM, \ SYSDB_NAME, NULL } struct group_info { const char *name; gid_t gid; bool mpg; const char **user_members; const char **memberofs; struct group_info **group_members; }; /*==================Helper routines to process results================= */ const char *rdn_as_string(TALLOC_CTX *mem_ctx, struct ldb_dn *dn) { const struct ldb_val *val; val = ldb_dn_get_rdn_val(dn); if (val == NULL) { return NULL; } return ldb_dn_escape_value(mem_ctx, *val);; } static int parse_memberofs(struct ldb_context *ldb, struct ldb_message_element *el, struct group_info *gi) { int i; struct ldb_dn *dn = NULL; gi->memberofs = talloc_array(gi, const char *, el->num_values+1); if (gi->memberofs == NULL) { return ENOMEM; } for (i = 0; i< el->num_values; ++i) { dn = ldb_dn_from_ldb_val(gi, ldb, &(el->values[i])); gi->memberofs[i] = talloc_strdup(gi, rdn_as_string(gi, dn)); talloc_zfree(dn); if (gi->memberofs[i] == NULL) { return ENOMEM; } DEBUG(6, ("memberof value: %s\n", gi->memberofs[i])); } gi->memberofs[el->num_values] = NULL; return EOK; } static int parse_members(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, struct sss_domain_info *domain, struct ldb_message_element *el, const char *parent_name, const char ***user_members, const char ***group_members, int *num_group_members) { struct ldb_dn *user_basedn = NULL, *group_basedn = NULL; struct ldb_dn *parent_dn = NULL; struct ldb_dn *dn = NULL; const char **um = NULL, **gm = NULL; unsigned int um_index = 0, gm_index = 0; TALLOC_CTX *tmp_ctx = NULL; int ret; int i; tmp_ctx = talloc_new(mem_ctx); if (!tmp_ctx) { ret = ENOMEM; goto fail; } user_basedn = ldb_dn_new_fmt(tmp_ctx, ldb, SYSDB_TMPL_USER_BASE, domain->name); group_basedn = ldb_dn_new_fmt(tmp_ctx, ldb, SYSDB_TMPL_GROUP_BASE, domain->name); if (!user_basedn || !group_basedn) { ret = ENOMEM; goto fail; } um = talloc_array(mem_ctx, const char *, el->num_values+1); gm = talloc_array(mem_ctx, const char *, el->num_values+1); if (!um || !gm) { ret = ENOMEM; goto fail; } for (i = 0; i< el->num_values; ++i) { dn = ldb_dn_from_ldb_val(tmp_ctx, ldb, &(el->values[i])); /* user member or group member? */ parent_dn = ldb_dn_get_parent(tmp_ctx, dn); if (ldb_dn_compare_base(parent_dn, user_basedn) == 0) { um[um_index] = rdn_as_string(mem_ctx, dn); if (um[um_index] == NULL) { ret = ENOMEM; goto fail; } DEBUG(6, ("User member %s\n", um[um_index])); um_index++; } else if (ldb_dn_compare_base(parent_dn, group_basedn) == 0) { gm[gm_index] = rdn_as_string(mem_ctx, dn); if (gm[gm_index] == NULL) { ret = ENOMEM; goto fail; } if (parent_name && strcmp(gm[gm_index], parent_name) == 0) { DEBUG(6, ("Skipping circular nesting for group %s\n", gm[gm_index])); continue; } DEBUG(6, ("Group member %s\n", gm[gm_index])); gm_index++; } else { DEBUG(2, ("Group member not a user nor group: %s\n", ldb_dn_get_linearized(dn))); ret = EIO; goto fail; } talloc_zfree(dn); talloc_zfree(parent_dn); } um[um_index] = NULL; gm[gm_index] = NULL; if (um_index > 0) { um = talloc_realloc(mem_ctx, um, const char *, um_index+1); if (!um) { ret = ENOMEM; goto fail; } } else { talloc_zfree(um); } if (gm_index > 0) { gm = talloc_realloc(mem_ctx, gm, const char *, gm_index+1); if (!gm) { ret = ENOMEM; goto fail; } } else { talloc_zfree(gm); } *user_members = um; *group_members = gm; *num_group_members = gm_index; talloc_zfree(tmp_ctx); return EOK; fail: talloc_zfree(um); talloc_zfree(gm); talloc_zfree(tmp_ctx); return ret; } static int process_group(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, struct ldb_message *msg, struct sss_domain_info *domain, const char *parent_name, struct group_info **info, const char ***group_members, int *num_group_members) { struct ldb_message_element *el; int ret; struct group_info *gi = NULL; DEBUG(6, ("Found entry %s\n", ldb_dn_get_linearized(msg->dn))); gi = talloc_zero(mem_ctx, struct group_info); if (!gi) { ret = ENOMEM; goto done; } /* mandatory data - name and gid */ gi->name = talloc_strdup(gi, ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL)); gi->gid = ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0); if (gi->gid == 0 || gi->name == NULL) { DEBUG(3, ("No name or no GID?\n")); ret = EIO; goto done; } /* list members */ el = ldb_msg_find_element(msg, SYSDB_MEMBER); if (el) { ret = parse_members(gi, ldb, domain, el, parent_name, &gi->user_members, group_members, num_group_members); if (ret != EOK) { goto done; } } /* list memberofs */ el = ldb_msg_find_element(msg, SYSDB_MEMBEROF); if (el) { ret = parse_memberofs(ldb, el, gi); if (ret != EOK) { goto done; } } *info = gi; return EOK; done: talloc_zfree(gi); return ret; } /*========Find info about a group and recursively about subgroups====== */ struct group_show_state { struct tevent_context *ev; struct sysdb_ctx *sysdb; struct sysdb_handle *handle; struct sss_domain_info *domain; struct group_info *root; bool recursive; }; static void group_show_root_done(struct tevent_req *subreq); static void group_show_recurse_done(struct tevent_req *subreq); struct tevent_req *group_show_recurse_send(TALLOC_CTX *, struct group_show_state *, struct group_info *, const char **, const int ); static int group_show_recurse_recv(TALLOC_CTX *, struct tevent_req *, struct group_info ***); struct tevent_req *group_show_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sysdb_ctx *sysdb, struct sysdb_handle *handle, struct sss_domain_info *domain, bool recursive, const char *name) { struct group_show_state *search_state = NULL; struct tevent_req *subreq = NULL; struct tevent_req *req = NULL; static const char *attrs[] = GROUP_SHOW_ATTRS; req = tevent_req_create(mem_ctx, &search_state, struct group_show_state); if (req == NULL) { return NULL; } search_state->ev = ev; search_state->sysdb = sysdb; search_state->handle = handle; search_state->domain = domain; search_state->recursive = recursive; /* First, search for the root group */ subreq = sysdb_search_group_by_name_send(search_state, search_state->ev, search_state->sysdb, search_state->handle, search_state->domain, name, attrs); if (!subreq) { talloc_zfree(req); return NULL; } tevent_req_set_callback(subreq, group_show_root_done, req); return req; } static void group_show_root_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct group_show_state *state = tevent_req_data(req, struct group_show_state); int ret; int i; struct ldb_message *msg = NULL; const char **group_members = NULL; int nmembers = 0; ret = sysdb_search_group_recv(subreq, state, &msg); talloc_zfree(subreq); if (ret) { DEBUG(2, ("Search failed: %s (%d)\n", strerror(ret), ret)); tevent_req_error(req, ret); return; } ret = process_group(state, sysdb_ctx_get_ldb(state->sysdb), msg, state->domain, NULL, &state->root, &group_members, &nmembers); if (ret != EOK) { DEBUG(2, ("Group processing failed: %s (%d)\n", strerror(ret), ret)); tevent_req_error(req, ret); return; } if (group_members == NULL) { tevent_req_done(req); return; } if (state->recursive == false) { /* if not recursive, just fill in names */ state->root->group_members = talloc_array(state->root, struct group_info *, nmembers+1); for (i=0; group_members[i]; i++) { state->root->group_members[i] = talloc_zero(state->root, struct group_info); if (!state->root->group_members) { tevent_req_error(req, ENOMEM); } state->root->group_members[i]->name = talloc_strdup(state->root, group_members[i]); if (!state->root->group_members[i]->name) { tevent_req_error(req, ENOMEM); } } state->root->group_members[nmembers] = NULL; tevent_req_done(req); return; } subreq = group_show_recurse_send(state->root, state, state->root, group_members, nmembers); if (!subreq) { tevent_req_error(req, ret); return; } tevent_req_set_callback(subreq, group_show_recurse_done, req); } static void group_show_recurse_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct group_show_state *state = tevent_req_data(req, struct group_show_state); int ret; ret = group_show_recurse_recv(state->root, subreq, &state->root->group_members); talloc_zfree(subreq); if (ret) { DEBUG(2, ("Recursive search failed: %s (%d)\n", strerror(ret), ret)); tevent_req_error(req, EIO); return; } tevent_req_done(req); } static int group_show_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, struct group_info **res) { struct group_show_state *state = tevent_req_data(req, struct group_show_state); TEVENT_REQ_RETURN_ON_ERROR(req); *res = talloc_move(mem_ctx, &state->root); return EOK; } /*==================Recursive search for nested groups================= */ struct group_show_recurse { const char **names; int current; struct group_info *parent; struct group_show_state *state; struct group_info **groups; }; static int group_show_recurse_search(struct tevent_req *, struct group_show_recurse *); static void group_show_recurse_next(struct tevent_req *); static void group_show_recurse_level_done(struct tevent_req *); static void group_show_recurse_cont(struct tevent_req *); struct tevent_req *group_show_recurse_send(TALLOC_CTX *mem_ctx, struct group_show_state *state, struct group_info *parent, const char **group_members, const int nmembers) { struct tevent_req *req = NULL; struct group_show_recurse *recurse_state = NULL; req = tevent_req_create(mem_ctx, &recurse_state, struct group_show_recurse); if (req == NULL) { return NULL; } recurse_state->current = 0; recurse_state->parent = parent; recurse_state->names = group_members; recurse_state->state = state; recurse_state->groups = talloc_array(state->root, struct group_info *, nmembers+1); /* trailing NULL */ if (!recurse_state->names || !recurse_state->names[recurse_state->current]) { talloc_zfree(req); return NULL; } if (group_show_recurse_search(req, recurse_state) != EOK) { talloc_zfree(req); return NULL; } return req; } static int group_show_recurse_search(struct tevent_req *req, struct group_show_recurse *recurse_state) { static const char *attrs[] = GROUP_SHOW_ATTRS; struct tevent_req *subreq = NULL; /* Skip circular groups */ if (strcmp(recurse_state->names[recurse_state->current], recurse_state->parent->name) == 0) { DEBUG(0, ("CIRCULAR DEP DETECTED\n")); group_show_recurse_cont(req); return EOK; } subreq = sysdb_search_group_by_name_send(recurse_state->state, recurse_state->state->ev, recurse_state->state->sysdb, recurse_state->state->handle, recurse_state->state->domain, recurse_state->names[recurse_state->current], attrs); if (!subreq) { return ENOMEM; } tevent_req_set_callback(subreq, group_show_recurse_next, req); return EOK; } static void group_show_recurse_next(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct group_show_recurse *recurse_state = tevent_req_data(req, struct group_show_recurse); const char **group_members = NULL; int nmembers = 0; struct ldb_message *msg = NULL; int ret; struct tevent_req *recurse_req = NULL; ret = sysdb_search_group_recv(subreq, recurse_state, &msg); talloc_zfree(subreq); if (ret) { DEBUG(2, ("Search failed: %s (%d)\n", strerror(ret), ret)); tevent_req_error(req, EIO); return; } ret = process_group(recurse_state->state->root, sysdb_ctx_get_ldb(recurse_state->state->sysdb), msg, recurse_state->state->domain, recurse_state->parent->name, &recurse_state->groups[recurse_state->current], &group_members, &nmembers); if (ret != EOK) { DEBUG(2, ("Group processing failed: %s (%d)\n", strerror(ret), ret)); tevent_req_error(req, ret); return; } /* descend to another level */ if (nmembers > 0) { recurse_req = group_show_recurse_send(recurse_state, recurse_state->state, recurse_state->groups[recurse_state->current], group_members, nmembers); if (!recurse_req) { tevent_req_error(req, ENOMEM); return; } /* to free group_members in the callback */ group_members = talloc_move(recurse_req, &group_members); tevent_req_set_callback(recurse_req, group_show_recurse_level_done, req); return; } /* Move to next group in the same level */ group_show_recurse_cont(req); } static void group_show_recurse_level_done(struct tevent_req *recurse_req) { int ret; struct tevent_req *req = tevent_req_callback_data(recurse_req, struct tevent_req); struct group_show_recurse *recurse_state = tevent_req_data(recurse_req, struct group_show_recurse); ret = group_show_recurse_recv(recurse_state->state->root, recurse_req, &recurse_state->parent->group_members); talloc_zfree(recurse_req); if (ret) { DEBUG(2, ("Recursive search failed: %s (%d)\n", strerror(ret), ret)); tevent_req_error(req, EIO); return; } /* Move to next group on the upper level */ group_show_recurse_cont(req); } static void group_show_recurse_cont(struct tevent_req *req) { struct group_show_recurse *recurse_state = tevent_req_data(req, struct group_show_recurse); int ret; recurse_state->current++; if (recurse_state->names[recurse_state->current] == NULL) { recurse_state->groups[recurse_state->current] = NULL; /* Sentinel */ tevent_req_done(req); return; } /* examine next group on the same level */ ret = group_show_recurse_search(req, recurse_state); if (ret != EOK) { tevent_req_error(req, ret); return; } } static int group_show_recurse_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, struct group_info ***out) { struct group_show_recurse *recurse_state = tevent_req_data(req, struct group_show_recurse); TEVENT_REQ_RETURN_ON_ERROR(req); *out = talloc_move(mem_ctx, &recurse_state->groups); return EOK; } /*==================Get info about MPG================================= */ struct group_show_mpg_state { struct ldb_context *ldb; struct group_info *info; }; static void group_show_mpg_done(struct tevent_req *); struct tevent_req *group_show_mpg_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sysdb_ctx *sysdb, struct sysdb_handle *handle, struct sss_domain_info *domain, const char *name) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct group_show_mpg_state *state; static const char *mpg_attrs[] = GROUP_SHOW_MPG_ATTRS; req = tevent_req_create(mem_ctx, &state, struct group_show_mpg_state); if (req == NULL) { return NULL; } state->ldb = sysdb_ctx_get_ldb(sysdb); subreq = sysdb_search_user_by_name_send(mem_ctx, ev, sysdb, handle, domain, name, mpg_attrs); if (!subreq) { talloc_zfree(req); return NULL; } tevent_req_set_callback(subreq, group_show_mpg_done, req); return req; } static void group_show_mpg_done(struct tevent_req *subreq) { int ret; struct ldb_message *msg = NULL; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct group_show_mpg_state *state = tevent_req_data(req, struct group_show_mpg_state); ret = sysdb_search_user_recv(subreq, req, &msg); talloc_zfree(subreq); if (ret) { DEBUG(2, ("Search failed: %s (%d)\n", strerror(ret), ret)); tevent_req_error(req, ret); return; } state->info = talloc_zero(state, struct group_info); if (!state->info) { tevent_req_error(req, ENOMEM); return; } state->info->name = talloc_strdup(state->info, ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL)); state->info->gid = ldb_msg_find_attr_as_uint64(msg, SYSDB_UIDNUM, 0); if (state->info->gid == 0 || state->info->name == NULL) { DEBUG(3, ("No name or no GID?\n")); tevent_req_error(req, EIO); return; } state->info->mpg = true; tevent_req_done(req); } static int group_show_mpg_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, struct group_info **res) { struct group_show_mpg_state *state = tevent_req_data(req, struct group_show_mpg_state); TEVENT_REQ_RETURN_ON_ERROR(req); *res = talloc_move(mem_ctx, &state->info); return EOK; } /*==================The main program=================================== */ struct sss_groupshow_state { struct group_info *root; int ret; bool done; }; static void sss_group_show_done(struct tevent_req *req) { int ret; struct sss_groupshow_state *sss_state = tevent_req_callback_data(req, struct sss_groupshow_state); ret = group_show_recv(sss_state, req, &sss_state->root); talloc_zfree(req); sss_state->ret = ret; sss_state->done = true; } static void sss_group_show_mpg_done(struct tevent_req *req) { int ret; struct sss_groupshow_state *sss_state = tevent_req_callback_data(req, struct sss_groupshow_state); ret = group_show_mpg_recv(sss_state, req, &sss_state->root); talloc_zfree(req); sss_state->ret = ret; sss_state->done = true; } static void print_group_info(struct group_info *g, int level) { int i; char padding[512]; char fmt[8]; snprintf(fmt, 8, "%%%ds", level*PADDING_SPACES); snprintf(padding, 512, fmt, ""); printf(_("%s%sGroup: %s\n"), padding, g->mpg ? _("Magic Private ") : "", g->name); printf(_("%sGID number: %d\n"), padding, g->gid); printf(_("%sMember users: "), padding); if (g->user_members) { for (i=0; g->user_members[i]; ++i) { printf("%s%s", i>0 ? "," : "", g->user_members[i]); } } printf(_("\n%sIs a member of: "), padding); if (g->memberofs) { for (i=0; g->memberofs[i]; ++i) { printf("%s%s", i>0 ? "," : "", g->memberofs[i]); } } printf(_("\n%sMember groups: "), padding); } static void print_recursive(struct group_info **group_members, int level) { int i; if (group_members == NULL) { return; } level++; for (i=0; group_members[i]; ++i) { printf("\n"); print_group_info(group_members[i], level); printf("\n"); print_recursive(group_members[i]->group_members, level); } } int main(int argc, const char **argv) { int ret = EXIT_SUCCESS; int pc_debug = 0; bool pc_recursive = false; const char *pc_groupname = NULL; struct tools_ctx *tctx = NULL; struct tevent_req *req = NULL; struct sss_groupshow_state *state = NULL; int i; poptContext pc = NULL; struct poptOption long_options[] = { POPT_AUTOHELP { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug, 0, _("The debug level to run with"), NULL }, { "recursive", 'R', POPT_ARG_NONE, NULL, 'r', _("Print indirect group members recursively"), NULL }, POPT_TABLEEND }; debug_prg_name = argv[0]; ret = set_locale(); if (ret != EOK) { DEBUG(1, ("set_locale failed (%d): %s\n", ret, strerror(ret))); ERROR("Error setting the locale\n"); ret = EXIT_FAILURE; goto fini; } /* parse ops_ctx */ pc = poptGetContext(NULL, argc, argv, long_options, 0); poptSetOtherOptionHelp(pc, "GROUPNAME"); while ((ret = poptGetNextOpt(pc)) > 0) { switch (ret) { case 'r': pc_recursive = true; break; } } debug_level = pc_debug; if (ret != -1) { usage(pc, poptStrerror(ret)); ret = EXIT_FAILURE; goto fini; } pc_groupname = poptGetArg(pc); if (pc_groupname == NULL) { usage(pc, _("Specify group to show\n")); ret = EXIT_FAILURE; goto fini; } CHECK_ROOT(ret, debug_prg_name); ret = init_sss_tools(&tctx); if (ret != EOK) { DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret))); if (ret == ENOENT) { ERROR("Error initializing the tools - no local domain\n"); } else { ERROR("Error initializing the tools\n"); } ret = EXIT_FAILURE; goto fini; } /* if the domain was not given as part of FQDN, default to local domain */ ret = parse_name_domain(tctx, pc_groupname); if (ret != EOK) { ERROR("Invalid domain specified in FQDN\n"); ret = EXIT_FAILURE; goto fini; } /* The search itself */ state = talloc_zero(tctx, struct sss_groupshow_state); if (!state) { goto fini; } req = group_show_send(tctx, tctx->ev, tctx->sysdb, tctx->handle, tctx->local, pc_recursive, tctx->octx->name); if (!req) { ERROR("Cannot initiate search\n"); ret = EXIT_FAILURE; goto fini; } tevent_req_set_callback(req, sss_group_show_done, state); while (!state->done) { tevent_loop_once(tctx->ev); } ret = state->ret; /* Also show MPGs */ if (ret == ENOENT) { state->done = false; state->ret = EOK; req = group_show_mpg_send(tctx, tctx->ev, tctx->sysdb, tctx->handle, tctx->local, tctx->octx->name); if (!req) { ERROR("Cannot initiate search\n"); ret = EXIT_FAILURE; goto fini; } tevent_req_set_callback(req, sss_group_show_mpg_done, state); while (!state->done) { tevent_loop_once(tctx->ev); } ret = state->ret; } /* Process result */ if (ret) { DEBUG(1, ("sysdb operation failed (%d)[%s]\n", ret, strerror(ret))); switch (ret) { case ENOENT: ERROR("No such group in local domain. " "Printing groups only allowed in local domain.\n"); break; default: ERROR("Internal error. Could not print group.\n"); break; } ret = EXIT_FAILURE; goto fini; } /* print the results */ print_group_info(state->root, 0); if (pc_recursive) { printf("\n"); print_recursive(state->root->group_members, 0); } else { if (state->root->group_members) { for (i=0; state->root->group_members[i]; ++i) { printf("%s%s", i>0 ? "," : "", state->root->group_members[i]->name); } } printf("\n"); } fini: talloc_free(tctx); poptFreeContext(pc); exit(ret); }