Skip to content

Commit 19e6190

Browse files
authored
Merge pull request #5714 from victormlg/CFE-2318-findusers
CFE-2318: Added findlocalusers() policy function
2 parents 3d784ee + 00d1094 commit 19e6190

File tree

6 files changed

+466
-1
lines changed

6 files changed

+466
-1
lines changed

examples/findlocalusers.cf

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#+begin_src cfengine3
2+
body common control
3+
{
4+
bundlesequence => { "example" };
5+
}
6+
bundle agent example
7+
{
8+
vars:
9+
"root_filter" slist => {"gid=0", "name=root"};
10+
"root_container" data => findlocalusers("@(root_filter)");
11+
"root_list" slist => getindices("root_container");
12+
13+
"bin_filter" data => '["name=bin"]';
14+
"bin_container" data => findlocalusers("@(bin_filter)");
15+
"bin_list" slist => getindices("bin_container");
16+
17+
reports:
18+
"List: $(root_list)";
19+
"List: $(bin_list)";
20+
}
21+
22+
#+end_src
23+
#############################################################################
24+
#+begin_src example_output
25+
#@ ```
26+
#@ R: List: root
27+
#@ R: List: bin
28+
#@ ```
29+
#+end_src

libpromises/evalfunction.c

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,170 @@ static FnCallResult FnCallGetUsers(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const
938938

939939
/*********************************************************************/
940940

941+
942+
#if defined(HAVE_GETPWENT) && !defined(__ANDROID__)
943+
944+
static FnCallResult FnCallFindLocalUsers(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
945+
{
946+
assert(fp != NULL);
947+
bool allocated = false;
948+
JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);
949+
950+
// we failed to produce a valid JsonElement, so give up
951+
if (json == NULL)
952+
{
953+
Log(LOG_LEVEL_ERR, "Function '%s' couldn't parse argument '%s'",
954+
fp->name, RlistScalarValueSafe(finalargs));
955+
return FnFailure();
956+
}
957+
else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
958+
{
959+
Log(LOG_LEVEL_ERR, "Bad argument '%s' in function '%s': Expected data container or slist",
960+
RlistScalarValueSafe(finalargs), fp->name);
961+
JsonDestroyMaybe(json, allocated);
962+
return FnFailure();
963+
}
964+
965+
JsonElement *parent = JsonObjectCreate(10);
966+
setpwent();
967+
struct passwd *pw;
968+
while ((pw = getpwent()) != NULL)
969+
{
970+
JsonIterator iter = JsonIteratorInit(json);
971+
bool can_add_to_json = true;
972+
JsonElement *element = JsonIteratorNextValue(&iter);
973+
while (element != NULL)
974+
{
975+
if (JsonGetElementType(element) != JSON_ELEMENT_TYPE_PRIMITIVE)
976+
{
977+
Log(LOG_LEVEL_ERR, "Bad argument '%s' in function '%s': Filter cannot include nested data",
978+
RlistScalarValueSafe(finalargs), fp->name);
979+
JsonDestroyMaybe(json, allocated);
980+
JsonDestroy(parent);
981+
return FnFailure();
982+
}
983+
const char *field = JsonPrimitiveGetAsString(element);
984+
const Rlist *tuple = RlistFromSplitString(field, '=');
985+
assert(tuple != NULL);
986+
const char *attribute = TrimWhitespace(RlistScalarValue(tuple));
987+
988+
if (tuple->next == NULL)
989+
{
990+
Log(LOG_LEVEL_ERR, "Invalid filter field '%s' in function '%s': Expected attributes and values to be separated with '='",
991+
fp->name, field);
992+
JsonDestroyMaybe(json, allocated);
993+
JsonDestroy(parent);
994+
return FnFailure();
995+
}
996+
const char *value = TrimWhitespace(RlistScalarValue(tuple->next));
997+
998+
if (StringEqual(attribute, "name"))
999+
{
1000+
if (!StringMatchFull(value, pw->pw_name))
1001+
{
1002+
can_add_to_json = false;
1003+
}
1004+
}
1005+
else if (StringEqual(attribute, "uid"))
1006+
{
1007+
char uid_string[PRINTSIZE(pw->pw_uid)];
1008+
int ret = snprintf(uid_string, sizeof(uid_string), "%u", pw->pw_uid);
1009+
1010+
if (ret < 0)
1011+
{
1012+
Log(LOG_LEVEL_ERR, "Couldn't convert the uid of '%s' to string in function '%s'",
1013+
pw->pw_name, fp->name);
1014+
JsonDestroyMaybe(json, allocated);
1015+
JsonDestroy(parent);
1016+
return FnFailure();
1017+
}
1018+
assert((size_t) ret < sizeof(uid_string));
1019+
1020+
if (!StringMatchFull(value, uid_string))
1021+
{
1022+
can_add_to_json = false;
1023+
}
1024+
}
1025+
else if (StringEqual(attribute, "gid"))
1026+
{
1027+
char gid_string[PRINTSIZE(pw->pw_uid)];
1028+
int ret = snprintf(gid_string, sizeof(gid_string), "%u", pw->pw_uid);
1029+
1030+
if (ret < 0)
1031+
{
1032+
Log(LOG_LEVEL_ERR, "Couldn't convert the gid of '%s' to string in function '%s'",
1033+
pw->pw_name, fp->name);
1034+
JsonDestroyMaybe(json, allocated);
1035+
JsonDestroy(parent);
1036+
return FnFailure();
1037+
}
1038+
assert((size_t) ret < sizeof(gid_string));
1039+
1040+
if (!StringMatchFull(value, gid_string))
1041+
{
1042+
can_add_to_json = false;
1043+
}
1044+
}
1045+
else if (StringEqual(attribute, "gecos"))
1046+
{
1047+
if (!StringMatchFull(value, pw->pw_gecos))
1048+
{
1049+
can_add_to_json = false;
1050+
}
1051+
}
1052+
else if (StringEqual(attribute, "dir"))
1053+
{
1054+
if ((!StringMatchFull(value, pw->pw_dir)))
1055+
{
1056+
can_add_to_json = false;
1057+
}
1058+
}
1059+
else if (StringEqual(attribute, "shell"))
1060+
{
1061+
if (!StringMatchFull(value, pw->pw_shell))
1062+
{
1063+
can_add_to_json = false;
1064+
}
1065+
}
1066+
else
1067+
{
1068+
Log(LOG_LEVEL_ERR, "Invalid attribute '%s' in function '%s': not supported",
1069+
attribute, fp->name);
1070+
JsonDestroyMaybe(json, allocated);
1071+
JsonDestroy(parent);
1072+
return FnFailure();
1073+
}
1074+
element = JsonIteratorNextValue(&iter);
1075+
}
1076+
if (can_add_to_json)
1077+
{
1078+
JsonElement *child = JsonObjectCreate(6);
1079+
JsonObjectAppendInteger(child, "uid", pw->pw_uid);
1080+
JsonObjectAppendInteger(child, "gid", pw->pw_gid);
1081+
JsonObjectAppendString(child, "gecos", pw->pw_gecos);
1082+
JsonObjectAppendString(child, "dir", pw->pw_dir);
1083+
JsonObjectAppendString(child, "shell", pw->pw_shell);
1084+
JsonObjectAppendObject(parent, pw->pw_name, child);
1085+
}
1086+
}
1087+
endpwent();
1088+
JsonDestroyMaybe(json, allocated);
1089+
1090+
return FnReturnContainerNoCopy(parent);
1091+
}
1092+
1093+
#else
1094+
1095+
static FnCallResult FnCallFindLocalUsers(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, ARG_UNUSED const Rlist *finalargs)
1096+
{
1097+
Log(LOG_LEVEL_ERR, "findlocalusers is not implemented");
1098+
return FnFailure();
1099+
}
1100+
1101+
#endif
1102+
1103+
/*********************************************************************/
1104+
9411105
static FnCallResult FnCallEscape(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
9421106
{
9431107
char buffer[CF_BUFSIZE];
@@ -10530,7 +10694,11 @@ static const FnCallArg IS_DATATYPE_ARGS[] =
1053010694
{CF_ANYSTRING, CF_DATA_TYPE_STRING, "Type"},
1053110695
{NULL, CF_DATA_TYPE_NONE, NULL}
1053210696
};
10533-
10697+
static const FnCallArg FIND_LOCAL_USERS_ARGS[] =
10698+
{
10699+
{CF_ANYSTRING, CF_DATA_TYPE_STRING, "Filter list"},
10700+
{NULL, CF_DATA_TYPE_NONE, NULL}
10701+
};
1053410702

1053510703
/*********************************************************/
1053610704
/* FnCalls are rvalues in certain promise constraints */
@@ -10870,6 +11038,8 @@ const FnCallType CF_FNCALL_TYPES[] =
1087011038
FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
1087111039
FnCallTypeNew("version_compare", CF_DATA_TYPE_CONTEXT, VERSION_COMPARE_ARGS, &FnCallVersionCompare, "Compare two version numbers with a specified operator",
1087211040
FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
11041+
FnCallTypeNew("findlocalusers", CF_DATA_TYPE_CONTAINER, FIND_LOCAL_USERS_ARGS, &FnCallFindLocalUsers, "Find matching local users",
11042+
FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
1087311043

1087411044
// Functions section following new naming convention
1087511045
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()",
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
body common control
2+
{
3+
inputs => { "../../default.cf.sub" };
4+
bundlesequence => { default("$(this.promise_filename)") };
5+
version => "1.0";
6+
}
7+
bundle agent init
8+
{
9+
vars:
10+
# simple filters
11+
"simple_filter" slist => { "name=root" };
12+
"number_filter" slist => { "uid=0" };
13+
14+
# longer filters
15+
"slist_filter" slist => { "gid=0", "name=root" };
16+
17+
# using data
18+
"data_filter" data => '[ "gid=0", "name=root" ]';
19+
20+
# using regex
21+
"simple_regex" slist => { "name=roo.*" };
22+
"number_regex" slist => { "uid=0.*" };
23+
"longer_regex" slist => { "name=ro.*", "uid=0.*" };
24+
25+
# non-existent user
26+
"unknown" slist => { "name=thisuserdoesntexist" };
27+
}
28+
bundle agent test
29+
{
30+
meta:
31+
"test_soft_fail" string => "windows",
32+
meta => { "CFE-2318" };
33+
34+
vars:
35+
"ulist1" data => findlocalusers("@(init.simple_filter)");
36+
"ulist2" data => findlocalusers("init.number_filter");
37+
"ulist4" data => findlocalusers("@(init.slist_filter)");
38+
"ulist3" data => findlocalusers("@(init.data_filter)");
39+
"ulist5" data => findlocalusers("@(init.simple_regex)");
40+
"ulist6" data => findlocalusers("@(init.number_regex)");
41+
"ulist7" data => findlocalusers("@(init.longer_regex)");
42+
"ulist8" data => findlocalusers("@(init.unknown)");
43+
44+
}
45+
bundle agent check
46+
{
47+
methods:
48+
"check" usebundle => dcs_check_state(test,
49+
"$(this.promise_filename).expected.json",
50+
$(this.promise_filename));
51+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{
2+
"ulist1": {
3+
"root": {
4+
"dir": "/root",
5+
"gecos": "root",
6+
"gid": 0,
7+
"shell": "/bin/bash",
8+
"uid": 0
9+
}
10+
},
11+
"ulist2": {
12+
"root": {
13+
"dir": "/root",
14+
"gecos": "root",
15+
"gid": 0,
16+
"shell": "/bin/bash",
17+
"uid": 0
18+
}
19+
},
20+
"ulist3": {
21+
"root": {
22+
"dir": "/root",
23+
"gecos": "root",
24+
"gid": 0,
25+
"shell": "/bin/bash",
26+
"uid": 0
27+
}
28+
},
29+
"ulist4": {
30+
"root": {
31+
"dir": "/root",
32+
"gecos": "root",
33+
"gid": 0,
34+
"shell": "/bin/bash",
35+
"uid": 0
36+
}
37+
},
38+
"ulist5": {
39+
"root": {
40+
"dir": "/root",
41+
"gecos": "root",
42+
"gid": 0,
43+
"shell": "/bin/bash",
44+
"uid": 0
45+
}
46+
},
47+
"ulist6": {
48+
"root": {
49+
"dir": "/root",
50+
"gecos": "root",
51+
"gid": 0,
52+
"shell": "/bin/bash",
53+
"uid": 0
54+
}
55+
},
56+
"ulist7": {
57+
"root": {
58+
"dir": "/root",
59+
"gecos": "root",
60+
"gid": 0,
61+
"shell": "/bin/bash",
62+
"uid": 0
63+
}
64+
},
65+
"ulist8": {
66+
}
67+
}

0 commit comments

Comments
 (0)