From ac7984fb384f3833dab04209a5380c8a457dcc32 Mon Sep 17 00:00:00 2001 From: Victor Moene Date: Mon, 22 Sep 2025 12:02:18 +0200 Subject: [PATCH 1/2] Added getgroupinfo policy function Ticket: CFE-4512 Changelog: Title Signed-off-by: Victor Moene --- libenv/sysinfo.c | 37 +++++++++++++++++++++++ libenv/sysinfo.h | 1 + libpromises/evalfunction.c | 60 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) diff --git a/libenv/sysinfo.c b/libenv/sysinfo.c index 49b45b5a6d..4b877f8171 100644 --- a/libenv/sysinfo.c +++ b/libenv/sysinfo.c @@ -3530,6 +3530,43 @@ JsonElement* GetUserInfo(const void *passwd) /*****************************************************************************/ +JsonElement* GetGroupInfo(const void *group) +{ +#ifdef __MINGW32__ + return NULL; + +#else /* !__MINGW32__ */ + + const struct group *gr = (struct group*) group; + + if (gr == NULL) + { + gr = getgrgid(getgid()); + } + + if (gr == NULL) + { + return NULL; + } + + JsonElement *result = JsonObjectCreate(3); + JsonObjectAppendString(result, "name", gr->gr_name); + JsonObjectAppendInteger(result, "gid", gr->gr_gid); + + JsonElement *mem = JsonArrayCreate(10); + for (int i = 0; gr->gr_mem[i] != NULL; i++) + { + JsonArrayAppendString(mem, gr->gr_mem[i]); + } + + JsonObjectAppendArray(result, "members", mem); + + return result; +#endif /* !__MINGW32__ */ +} + +/*****************************************************************************/ + void GetSysVars(EvalContext *ctx) { /* Get info for current user. */ diff --git a/libenv/sysinfo.h b/libenv/sysinfo.h index 825a661bcb..b85731d73c 100644 --- a/libenv/sysinfo.h +++ b/libenv/sysinfo.h @@ -40,5 +40,6 @@ void GetNetworkingInfo(EvalContext *ctx); JsonElement* GetNetworkingConnections(EvalContext *ctx); JsonElement* GetUserInfo(const void *passwd); +JsonElement* GetGroupInfo(const void *group); #endif diff --git a/libpromises/evalfunction.c b/libpromises/evalfunction.c index c5249c3b7d..6f36b30ff6 100644 --- a/libpromises/evalfunction.c +++ b/libpromises/evalfunction.c @@ -1381,6 +1381,57 @@ static FnCallResult FnCallGetUserInfo(ARG_UNUSED EvalContext *ctx, ARG_UNUSED co /*********************************************************************/ + +static FnCallResult FnCallGetGroupInfo(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ +#ifdef __MINGW32__ + return FnFailure(); + +#else /* !__MINGW32__ */ + + struct group *gr = NULL; + + if (finalargs == NULL) + { + gr = getgrgid(getgid()); + } + else + { + char *arg = RlistScalarValue(finalargs); + if (StringIsNumeric(arg)) + { + gid_t gid = Str2Gid(arg, NULL, NULL); + if (gid == CF_SAME_GROUP) // user "*" + { + gid = getgid(); + } + else if (gid == CF_UNKNOWN_GROUP) + { + Log(LOG_LEVEL_ERR, "The specified group '%s' doesn't exist", arg); + return FnFailure(); + } + + gr = getgrgid(gid); + } + else + { + gr = getgrnam(arg); + } + } + + JsonElement *result = GetGroupInfo(gr); + + if (result == NULL) + { + return FnFailure(); + } + + return FnReturnContainerNoCopy(result); +#endif +} + +/*********************************************************************/ + static FnCallResult FnCallGetUid(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) { #ifdef __MINGW32__ @@ -10426,6 +10477,13 @@ static const FnCallArg GETUSERINFO_ARGS[] = {NULL, CF_DATA_TYPE_NONE, NULL} }; +static const FnCallArg GETGROUPINFO_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Group name or group ID as string"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + + static const FnCallArg GREP_ARGS[] = { {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"}, @@ -11367,6 +11425,8 @@ const FnCallType CF_FNCALL_TYPES[] = FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL), FnCallTypeNew("getuserinfo", CF_DATA_TYPE_CONTAINER, GETUSERINFO_ARGS, &FnCallGetUserInfo, "Get a data container describing user arg1, defaulting to current user", 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("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 1b4f0da2218b79ff7044c34785a650a14230848e Mon Sep 17 00:00:00 2001 From: Victor Moene Date: Mon, 22 Sep 2025 12:05:52 +0200 Subject: [PATCH 2/2] Added tests for getgroupinfo Signed-off-by: Victor Moene --- .../01_vars/02_functions/getgroupinfo.cf | 31 +++++++++++++++++++ .../getgroupinfo.cf.expected.json | 4 +++ 2 files changed, 35 insertions(+) create mode 100644 tests/acceptance/01_vars/02_functions/getgroupinfo.cf create mode 100644 tests/acceptance/01_vars/02_functions/getgroupinfo.cf.expected.json diff --git a/tests/acceptance/01_vars/02_functions/getgroupinfo.cf b/tests/acceptance/01_vars/02_functions/getgroupinfo.cf new file mode 100644 index 0000000000..68c9ae7eb3 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getgroupinfo.cf @@ -0,0 +1,31 @@ +####################################################### +# +# Test 'getgroupinfo' function +# +####################################################### + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "test_soft_fail" string => "windows", + meta => { "ENT-10217" }; + vars: + # this is pretty much all we can test across platforms + "info_root" string => nth(getgroupinfo("root"), "name"); + "info_0" string => nth(getgroupinfo(0), "gid"); +} + +bundle agent check +{ + methods: + "check" usebundle => dcs_check_state(test, + "$(this.promise_filename).expected.json", + "$(this.promise_filename)"); +} diff --git a/tests/acceptance/01_vars/02_functions/getgroupinfo.cf.expected.json b/tests/acceptance/01_vars/02_functions/getgroupinfo.cf.expected.json new file mode 100644 index 0000000000..252e46f8c9 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/getgroupinfo.cf.expected.json @@ -0,0 +1,4 @@ +{ + "info_0": "0", + "info_root": "root" +}