From b1fb08165f7878aedefc2d7da3da68988f770a31 Mon Sep 17 00:00:00 2001 From: Victor Moene Date: Wed, 19 Aug 2026 16:48:41 +0200 Subject: [PATCH 1/2] Added cf_popen_exact_args cf_popen_exact_args is variation of cf_popen when the exact arguments are known at compile time. This skips the quote escaping of ArgSplitCommand which is needed when argv can be of arbitrary length, such as with user input Signed-off-by: Victor Moene --- libpromises/pipes.h | 2 ++ libpromises/pipes_unix.c | 27 ++++++++++++++++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/libpromises/pipes.h b/libpromises/pipes.h index 5961ed0140..f5756e45b6 100644 --- a/libpromises/pipes.h +++ b/libpromises/pipes.h @@ -53,6 +53,8 @@ FILE *cf_popensetuid(const char *command, const Seq *arglist, const char *type, FILE *cf_popen_sh(const char *command, const char *type); FILE *cf_popen_sh_select(const char *command, const char *type, OutputSelect output_select); FILE *cf_popen_shsetuid(const char *command, const char *type, uid_t uid, gid_t gid, char *chdirv, char *chrootv, int background); +FILE *cf_popen_exact_args_select(const char **argv, const char *type, OutputSelect output_select); +FILE *cf_popen_exact_args(const char **argv, const char *type, bool capture_stderr); int cf_pclose(FILE *pp); void cf_pclose_nowait(FILE *pp); bool PipeToPid(pid_t *pid, FILE *pp); diff --git a/libpromises/pipes_unix.c b/libpromises/pipes_unix.c index a9aece3778..1e9a1b7ba7 100644 --- a/libpromises/pipes_unix.c +++ b/libpromises/pipes_unix.c @@ -371,18 +371,16 @@ IOData cf_popen_full_duplex(const char *command, bool capture_stderr, bool requi } } -FILE *cf_popen_select(const char *command, const char *type, OutputSelect output_select) +// do not use with user input +FILE *cf_popen_exact_args_select(const char **argv, const char *type, OutputSelect output_select) { int pd[2]; pid_t pid; FILE *pp = NULL; - char **argv = ArgSplitCommand(command, NULL); - pid = CreatePipeAndFork(type, pd); if (pid == (pid_t) -1) { - ArgFree(argv); return NULL; } @@ -427,7 +425,6 @@ FILE *cf_popen_select(const char *command, const char *type, OutputSelect output if ((pp = fdopen(pd[0], type)) == NULL) { cf_pwait(pid); - ArgFree(argv); return NULL; } break; @@ -439,13 +436,11 @@ FILE *cf_popen_select(const char *command, const char *type, OutputSelect output if ((pp = fdopen(pd[1], type)) == NULL) { cf_pwait(pid); - ArgFree(argv); return NULL; } } ChildrenFDSet(fileno(pp), pid); - ArgFree(argv); return pp; } @@ -453,6 +448,24 @@ FILE *cf_popen_select(const char *command, const char *type, OutputSelect output return NULL; } +// do not use with user input +FILE *cf_popen_exact_args(const char **argv, const char *type, bool capture_stderr) +{ + return cf_popen_exact_args_select( + argv, + type, + capture_stderr ? OUTPUT_SELECT_BOTH : OUTPUT_SELECT_STDOUT); +} + +FILE *cf_popen_select(const char *command, const char *type, OutputSelect output_select) +{ + char **argv = ArgSplitCommand(command, NULL); + FILE *ret = cf_popen_exact_args_select(argv, type, output_select); + ArgFree(argv); + + return ret; +} + FILE *cf_popen(const char *command, const char *type, bool capture_stderr) { return cf_popen_select( From 84fdb4cbe79375cef3adedc673d93b97898a3982 Mon Sep 17 00:00:00 2001 From: Victor Moene Date: Wed, 19 Aug 2026 16:52:13 +0200 Subject: [PATCH 2/2] Added strtotime policy function Ticket: CFE-3882 Changeglog: Title Signed-off-by: Victor Moene --- libpromises/evalfunction.c | 103 ++++++++++++++++++ .../01_vars/02_functions/strtotime.cf | 29 +++++ 2 files changed, 132 insertions(+) create mode 100644 tests/acceptance/01_vars/02_functions/strtotime.cf diff --git a/libpromises/evalfunction.c b/libpromises/evalfunction.c index 9662bcd9e8..2c5dee9ba2 100644 --- a/libpromises/evalfunction.c +++ b/libpromises/evalfunction.c @@ -7597,6 +7597,101 @@ static FnCallResult FnCallStrftime(ARG_UNUSED EvalContext *ctx, /*********************************************************************/ +static const char *date_paths[] = { + "/usr/bin/date", + "/bin/date", + NULL +}; + +static const char *LocateDateBinary() +{ + for (size_t i = 0; date_paths[i] != NULL; i++) + { + const char *path = date_paths[i]; + + if (IsExecutable(path)) + { + return path; + } + } + return NULL; +} + +static int ParseDate(const char *input_string, time_t *out) +{ + const char *date_path = LocateDateBinary(); + + if (date_path == NULL) + { + Log(LOG_LEVEL_ERR, "Unable to find 'date' binary"); + return -1; + } + + char buffer[MAX_INPUT]; + int n = snprintf(buffer, sizeof(buffer), "--date=%s", input_string); + + if (n < 0 || (size_t) n >= sizeof(buffer)) { + Log(LOG_LEVEL_ERR, "Truncation error: input string '%.10s...' is too long (%d >= %zu)", + input_string, n, sizeof(buffer)); + return -1; + } + + const char *argv[] = {date_path, buffer, "+%s", NULL}; + FILE *fd = cf_popen_exact_args(argv, "r", true); + + if (fd == NULL) + { + Log(LOG_LEVEL_ERR, "Couldn't run date \"--date='%s' +%%s\"", input_string); + return -1; + } + + size_t bytes_read = fread(buffer, 1, sizeof(buffer) - 1 , fd); + buffer[bytes_read] = '\0'; + + if (bytes_read == 0) + { + if (ferror(fd)) + { + Log(LOG_LEVEL_ERR, "Error reading output for '%s'", input_string); + } + else if (feof(fd)) + { + Log(LOG_LEVEL_DEBUG, "No output read for '%s'", input_string); + } + fclose(fd); + return -1; + } + fclose(fd); + + int ret = StringToLong(buffer, (long *) out); + if (ret != 0) + { + LogStringToLongError(buffer, "ParseDate", ret); + return -1; + } + + return 0; +} + +static FnCallResult FnCallStrToTime(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + assert(fp != NULL); + + const char *input_string = RlistScalarValue(finalargs); + time_t result; + int ret = ParseDate(input_string, &result); + + if (ret != 0) + { + Log(LOG_LEVEL_ERR, "'%s': Invalid date '%s'", fp->name, input_string); + return FnFailure(); + } + + return FnReturnF("%ld", result); +} + +/*********************************************************************/ + static FnCallResult FnCallEval(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) { if (finalargs == NULL) @@ -11427,6 +11522,12 @@ static const FnCallArg STRFTIME_ARGS[] = {NULL, CF_DATA_TYPE_NONE, NULL} }; +static const FnCallArg STRTOTIME_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "String to parse"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + static const FnCallArg STRING_REPLACE_ARGS[] = { {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Source string"}, @@ -12003,6 +12104,8 @@ const FnCallType CF_FNCALL_TYPES[] = FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL, DEFAULT_ARGC), FnCallTypeNew("strftime", CF_DATA_TYPE_STRING, STRFTIME_ARGS, &FnCallStrftime, "Format a date and time string", FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL, DEFAULT_ARGC), + FnCallTypeNew("strtotime", CF_DATA_TYPE_INT, STRTOTIME_ARGS, &FnCallStrToTime, "Parse a timestamp from a string", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL, DEFAULT_ARGC), FnCallTypeNew("sublist", CF_DATA_TYPE_STRING_LIST, SUBLIST_ARGS, &FnCallSublist, "Returns arg3 element from either the head or the tail (according to arg2) of list or array or data container arg1.", FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL, DEFAULT_ARGC), FnCallTypeNew("sysctlvalue", CF_DATA_TYPE_STRING, SYSCTLVALUE_ARGS, &FnCallSysctlValue, "Returns a value for sysctl key arg1 pair", diff --git a/tests/acceptance/01_vars/02_functions/strtotime.cf b/tests/acceptance/01_vars/02_functions/strtotime.cf new file mode 100644 index 0000000000..1b6d5eb98a --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/strtotime.cf @@ -0,0 +1,29 @@ +####################################################### +# +# Test strtotime function +# +####################################################### +body common control +{ + inputs => { "../../default.sub.cf" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### +bundle agent test +{ + vars: + "some_date" int => strtotime("2009-09-21T00:00:00Z"); + "epoch" int => int(1253491200); + + classes: + "ok" expression => strcmp("$(epoch)", "$(some_date)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +}