Skip to content

Commit 00d1094

Browse files
committed
CFE-2318: Added findLocalUsers function
Changelog: Added findlocalusers policy function that returns all the local users matching certain attributes Ticket: CFE-2318 Signed-off-by: Victor Moene <victor.moene@northern.tech>
1 parent 552e928 commit 00d1094

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
@@ -923,6 +923,170 @@ static FnCallResult FnCallGetUsers(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const
923923

924924
/*********************************************************************/
925925

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

1047210640
/*********************************************************/
1047310641
/* FnCalls are rvalues in certain promise constraints */
@@ -10803,6 +10971,8 @@ const FnCallType CF_FNCALL_TYPES[] =
1080310971
FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
1080410972
FnCallTypeNew("version_compare", CF_DATA_TYPE_CONTEXT, VERSION_COMPARE_ARGS, &FnCallVersionCompare, "Compare two version numbers with a specified operator",
1080510973
FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
10974+
FnCallTypeNew("findlocalusers", CF_DATA_TYPE_CONTAINER, FIND_LOCAL_USERS_ARGS, &FnCallFindLocalUsers, "Find matching local users",
10975+
FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
1080610976

1080710977
// Functions section following new naming convention
1080810978
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)