Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 159 additions & 2 deletions libpromises/evalfunction.c
Original file line number Diff line number Diff line change
Expand Up @@ -1074,8 +1074,8 @@ static FnCallResult FnCallFindLocalUsers(EvalContext *ctx, ARG_UNUSED const Poli
}
else if (StringEqual(attribute, "gid"))
{
char gid_string[PRINTSIZE(pw->pw_uid)];
int ret = snprintf(gid_string, sizeof(gid_string), "%u", pw->pw_uid);
char gid_string[PRINTSIZE(pw->pw_gid)];
int ret = snprintf(gid_string, sizeof(gid_string), "%u", pw->pw_gid);

if (ret < 0)
{
Expand Down Expand Up @@ -1152,6 +1152,161 @@ static FnCallResult FnCallFindLocalUsers(ARG_UNUSED EvalContext *ctx, ARG_UNUSED

/*********************************************************************/

#if defined(HAVE_GETPWENT) && !defined(__ANDROID__)

static bool GroupContainsMember(const char *member, const struct group *gr)
{
// if the group doesn't have any members
assert(gr != NULL);
if (gr->gr_mem[0] == NULL)
{
return StringMatchFull(member, "");
}
Comment thread
larsewi marked this conversation as resolved.
bool contains = false;
for (int i = 0; gr->gr_mem[i] != NULL; i++)
{
if (StringMatchFull(member, gr->gr_mem[i]))
{
contains = true;
}
}
return contains;
}

static bool GroupMatchesFilter(const struct group *gr, JsonElement *filter)
{
assert(gr != NULL);

bool group_matches_filter = true;
JsonIterator iter = JsonIteratorInit(filter);
JsonElement *element = JsonIteratorNextValue(&iter);

while (element != NULL)
{
if (JsonGetElementType(element) != JSON_ELEMENT_TYPE_PRIMITIVE)
{
Log(LOG_LEVEL_ERR, "Bad argument: Filter cannot include nested data");
return false;
}
const char *field = JsonPrimitiveGetAsString(element);
const Rlist *tuple = RlistFromSplitString(field, '=');
assert(tuple != NULL);
const char *attribute = TrimWhitespace(RlistScalarValue(tuple));

if (tuple->next == NULL)
{
Log(LOG_LEVEL_ERR, "Invalid filter field '%s': Expected attributes and values to be separated with '='",
field);
return false;
}
const char *value = TrimWhitespace(RlistScalarValue(tuple->next));

if (StringEqual(attribute, "name"))
{
if (!StringMatchFull(value, gr->gr_name))
Comment thread Fixed
{
group_matches_filter = false;
}
}
else if (StringEqual(attribute, "gid"))
{
char gid_string[PRINTSIZE(gr->gr_gid)];
int ret = snprintf(gid_string, sizeof(gid_string), "%u", gr->gr_gid);
Comment thread Fixed

if (ret < 0)
{
Log(LOG_LEVEL_ERR, "Couldn't convert the gid of '%s'",
gr->gr_name);
Comment thread Fixed
return false;
}
assert((size_t) ret < sizeof(gid_string));

if (!StringMatchFull(value, gid_string))
{
group_matches_filter = false;
}
}
else if (StringEqual(attribute, "members"))
{
Rlist *members = RlistFromSplitString(value, ',');

// we match all groups whose list of members contains the filter's members
while (members != NULL)
{
group_matches_filter &= GroupContainsMember(members->val.item, gr);
members = members->next;
}
}
else
{
Log(LOG_LEVEL_ERR, "Invalid attribute '%s': not supported",
attribute);
return false;
}
element = JsonIteratorNextValue(&iter);
}
return group_matches_filter;
}

static FnCallResult FnCallFindLocalGroups(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
Comment thread Fixed
{
assert(fp != NULL);
bool allocated = false;
JsonElement *filter = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);

// we failed to produce a valid JsonElement, so give up
if (filter == NULL)
{
Log(LOG_LEVEL_ERR, "Function '%s' couldn't parse argument '%s'",
fp->name, RlistScalarValueSafe(finalargs));
return FnFailure();
}
else if (JsonGetElementType(filter) != JSON_ELEMENT_TYPE_CONTAINER)
{
Log(LOG_LEVEL_ERR, "Bad argument '%s' in function '%s': Expected data container or slist",
RlistScalarValueSafe(finalargs), fp->name);
JsonDestroyMaybe(filter, allocated);
return FnFailure();
}

JsonElement *parent = JsonObjectCreate(10);
setgrent();
const struct group *gr;
while ((gr = getgrent()) != NULL)
Comment thread
victormlg marked this conversation as resolved.
{
if (GroupMatchesFilter(gr, filter))
{
JsonElement *child = JsonObjectCreate(2);
JsonObjectAppendInteger(child, "gid", gr->gr_gid);

JsonElement *member_array = JsonArrayCreate(10);
for (int i = 0; gr->gr_mem[i] != NULL; i++)
{
JsonArrayAppendString(member_array, gr->gr_mem[i]);
}
JsonObjectAppendArray(child, "members", member_array);
JsonObjectAppendObject(parent, gr->gr_name, child);
}
}
endgrent();
JsonDestroyMaybe(filter, allocated);

return FnReturnContainerNoCopy(parent);
}

#else

static FnCallResult FnCallFindLocalGroups(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, ARG_UNUSED const Rlist *finalargs)
{
Log(LOG_LEVEL_ERR, "findlocalgroups is not implemented on this platform");
return FnFailure();
}

#endif

/*********************************************************************/


static FnCallResult FnCallEscape(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
{
char buffer[CF_BUFSIZE];
Expand Down Expand Up @@ -11598,6 +11753,8 @@ const FnCallType CF_FNCALL_TYPES[] =
FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
FnCallTypeNew("findlocalusers", CF_DATA_TYPE_CONTAINER, FIND_LOCAL_USERS_ARGS, &FnCallFindLocalUsers, "Find matching local users",
FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
FnCallTypeNew("findlocalgroups", CF_DATA_TYPE_CONTAINER, FIND_LOCAL_USERS_ARGS, &FnCallFindLocalGroups, "Find matching local groups",
FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),

// Functions section following new naming convention
FnCallTypeNew("string_mustache", CF_DATA_TYPE_STRING, STRING_MUSTACHE_ARGS, &FnCallStringMustache, "Expand a Mustache template from arg1 into a string using the optional data container in arg2 or datastate()",
Expand Down
68 changes: 68 additions & 0 deletions tests/acceptance/01_vars/02_functions/findlocalgroups.cf
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
body common control
{
inputs => { "../../default.cf.sub" };
bundlesequence => { default("$(this.promise_filename)") };
version => "1.0";
}

bundle agent init
{
vars:
# simple filters
"simple_filter"
slist => { "name=root" };
"number_filter"
slist => { "gid=0" };
# longer filters
"slist_filter"
slist => { "gid=0", "name=root" };
# using data
"data_filter"
data => '[ "gid=0", "name=root" ]';
# using regex
"simple_regex"
slist => { "name=roo.*" };
"number_regex"
slist => { "gid=0.*" };
"longer_regex"
slist => { "name=ro.*", "gid=0.*" };
# non-existent group
"unknown"
slist => { "name=thisgroupdoesntexist" };
}

bundle agent test
{
meta:
"test_soft_fail"
string => "!linux|(termux|android)",
meta => { "CFE-2318" };
vars:
"glist1"
data => findlocalgroups("@(init.simple_filter)");
"glist2"
data => findlocalgroups("@(init.number_filter)");
"glist4"
data => findlocalgroups("@(init.slist_filter)");
"glist3"
data => findlocalgroups("@(init.data_filter)");
"glist5"
data => findlocalgroups("@(init.simple_regex)");
"glist6"
data => findlocalgroups("@(init.number_regex)");
"glist7"
data => findlocalgroups("@(init.longer_regex)");
"glist8"
data => findlocalgroups("@(init.unknown)");
}

bundle agent check
{
methods:
"check"
usebundle => dcs_check_state(
test,
"$(this.promise_filename).expected.json",
$(this.promise_filename)
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"glist1": {
"root": {
"gid": 0,
"members": []
}
},
"glist2": {
"root": {
"gid": 0,
"members": []
}
},
"glist3": {
"root": {
"gid": 0,
"members": []
}
},
"glist4": {
"root": {
"gid": 0,
"members": []
}
},
"glist5": {
"root": {
"gid": 0,
"members": []
}
},
"glist6": {
"root": {
"gid": 0,
"members": []
}
},
"glist7": {
"root": {
"gid": 0,
"members": []
}
},
"glist8": {
}
}
Loading