From 0257ca99c9cffa37b8e308100c13cc8dad86c003 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 5961ed0140f..f5756e45b63 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 a9aece3778f..1e9a1b7ba74 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 d2df10ae9a38bf31fdfd099a64473bbc8b9bdb22 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 9662bcd9e80..efd35e3b1b2 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[CF_BUFSIZE]; + 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 00000000000..1b6d5eb98ac --- /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"; +}