From 7e84944f0c5eb34c4f7394df569bcec25e412f13 Mon Sep 17 00:00:00 2001 From: Victor Moene Date: Mon, 22 Sep 2025 10:18:23 +0200 Subject: [PATCH 1/2] Added getgroups policy function Ticket: ENT-12722 Changelog: Title Signed-off-by: Victor Moene --- libpromises/evalfunction.c | 57 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/libpromises/evalfunction.c b/libpromises/evalfunction.c index 6f36b30ff6..1eaaa029c1 100644 --- a/libpromises/evalfunction.c +++ b/libpromises/evalfunction.c @@ -989,6 +989,54 @@ static FnCallResult FnCallGetUsers(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const /*********************************************************************/ +#if defined(HAVE_GETPWENT) && !defined(__ANDROID__) + +static FnCallResult FnCallGetGroups(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + assert(finalargs != NULL); + const char *except_name = RlistScalarValue(finalargs); + const char *except_uid = RlistScalarValue(finalargs->next); + + Rlist *except_names = RlistFromSplitString(except_name, ','); + Rlist *except_uids = RlistFromSplitString(except_uid, ','); + + setgrent(); + + Rlist *newlist = NULL; + struct group *gr; + while ((gr = getgrent()) != NULL) + { + char *gid_str = StringFromLong((int) gr->gr_gid); + + if (!RlistKeyIn(except_names, gr->gr_name) && !RlistKeyIn(except_uids, gid_str)) + { + RlistAppendScalarIdemp(&newlist, gr->gr_name); + } + + free(gid_str); + } + + endgrent(); + + RlistDestroy(except_names); + RlistDestroy(except_uids); + + return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } }; +} + +#else + +static FnCallResult FnCallGetGroups(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, ARG_UNUSED const Rlist *finalargs) +{ + Log(LOG_LEVEL_ERR, "getgroups is not supported"); + return FnFailure(); +} + +#endif + +/*********************************************************************/ + + #if defined(HAVE_GETPWENT) && !defined(__ANDROID__) static FnCallResult FnCallFindLocalUsers(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) @@ -10445,6 +10493,13 @@ static const FnCallArg GETUSERS_ARGS[] = {NULL, CF_DATA_TYPE_NONE, NULL} }; +static const FnCallArg GETGROUPS_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Comma separated list of Group names"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Comma separated list of GroupID numbers"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + static const FnCallArg GETENV_ARGS[] = { {CF_IDRANGE, CF_DATA_TYPE_STRING, "Name of environment variable"}, @@ -11427,6 +11482,8 @@ const FnCallType CF_FNCALL_TYPES[] = FNCALL_OPTION_VARARG, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL), FnCallTypeNew("getgroupinfo", CF_DATA_TYPE_CONTAINER, GETGROUPINFO_ARGS, &FnCallGetGroupInfo, "Get a data container describing specified group, or current group if not specified", FNCALL_OPTION_VARARG, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("getgroups", CF_DATA_TYPE_STRING_LIST, GETGROUPS_ARGS, &FnCallGetGroups, "Get a list of all system groups defined, minus those names defined in the first argument and group IDs defined in the second argument", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL), FnCallTypeNew("getvalues", CF_DATA_TYPE_STRING_LIST, GETINDICES_ARGS, &FnCallGetValues, "Get a list of values in the list or array or data container arg1", FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), FnCallTypeNew("getvariablemetatags", CF_DATA_TYPE_STRING_LIST, GETVARIABLEMETATAGS_ARGS, &FnCallGetMetaTags, "Collect the variable arg1's meta tags into an slist, optionally collecting only tag key arg2", From f58ce1797cc2d58935edebcad5dbae3eebbba006 Mon Sep 17 00:00:00 2001 From: Victor Moene Date: Mon, 22 Sep 2025 11:31:23 +0200 Subject: [PATCH 2/2] Added test to getgroups Signed-off-by: Victor Moene --- .../01_vars/02_functions/getgroups.cf | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/acceptance/01_vars/02_functions/getgroups.cf diff --git a/tests/acceptance/01_vars/02_functions/getgroups.cf b/tests/acceptance/01_vars/02_functions/getgroups.cf new file mode 100644 index 0000000000..c22bdc8c9f --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getgroups.cf @@ -0,0 +1,58 @@ +####################################################### +# +# Test getgroups(), list content +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { check }; + version => "1.0"; +} + +bundle agent check +{ + meta: + "description" -> { "ENT-12722" } + string => "Test whether the entries of getroups() are like the ones inside /etc/group"; + "test_skip_unsupported" + string => "windows"; + vars: + "group_entries" + slist => splitstring(readfile("/etc/group"), "\n", 10); + "actual_groups_unfiltered" + slist => maplist( + nth(splitstring("$(this)", ":", 2), 0), "@(group_entries)" + ); + # slice out the 3 first entries: root, bin, daemon + "actual_groups" + slist => sublist("@(actual_groups_unfiltered)", "tail", 7); + # get the first 7 groups, without root, bin and deamon + "retrieved_groups_arg1" + slist => sublist(getgroups("root,daemon,bin", ""), "head", 7); + "retrieved_groups_arg2" + slist => sublist(getgroups("", "0,1,2"), "head", 7); + "retrieved_groups_arg1_arg2" + slist => sublist(getgroups("root,daemon", "1,2"), "head", 7); + "actual_groups_content" + string => join(" ", "@(actual_groups)"); + "retrieved_groups_content_arg1" + string => join(" ", "@(retrieved_groups_arg1)"); + "retrieved_groups_content_arg2" + string => join(" ", "@(retrieved_groups_arg2)"); + "retrieved_groups_content_arg1_arg2" + string => join(" ", "@(retrieved_groups_arg1_arg2)"); + classes: + "ok" + expression => and( + strcmp("$(actual_groups_content)", "$(retrieved_groups_content_arg1)"), + strcmp("$(actual_groups_content)", "$(retrieved_groups_content_arg2)"), + strcmp("$(actual_groups_content)", "$(retrieved_groups_content_arg1_arg2)") + ); + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +}