diff --git a/man/remind.1.in b/man/remind.1.in index cf0580e8..6900972c 100644 --- a/man/remind.1.in +++ b/man/remind.1.in @@ -4308,7 +4308,7 @@ Returns a string that is the ordinal number \fInum\fR. For example, In order to help with localization, if you define a function called \fBordx\fR that takes a single parameter, then calling \fBord\fR(\fIn\fR) invokes \fBordx\fR(\fIn\fR) and returns whatever -it does. +it does. During the callback to \fBordx\fR, \fBRUN\fR will be disabled. .RE .TP .B orthodoxeaster([dqi_arg]) @@ -6516,6 +6516,9 @@ If you use a \fB%{name}\fR sequence and the function \fBsubst_\fIname\fR is not defined or returns an error, then \fB%{name}\fR is replaced with the empty string. .PP +Note that when \fBRemind\fR invokes any callback function for a +substitution sequence, \fBRUN\fR will be disabled. +.PP .SH THE TRANSLATION TABLE .PP To assist with localizing reminder files, \fBRemind\fR maintains a diff --git a/src/dosubst.c b/src/dosubst.c index 361867f3..1a6877ac 100644 --- a/src/dosubst.c +++ b/src/dosubst.c @@ -145,7 +145,7 @@ int DoSubst(ParsePtr p, DynamicBuffer *dbuf, Trigger *t, TimeTrig const *tt, int if (func && check_subst_args(func, 1)) { snprintf(s, sizeof(s), "subst_ampm(%d)", h); expr = (char const *) s; - r = EvalExpr(&expr, &v, NULL); + r = EvalExprRunDisabled(&expr, &v, NULL); if (r == OK) { if (!DoCoerce(STR_TYPE, &v)) { snprintf(mypm, sizeof(mypm), "%s", v.v.str); @@ -173,7 +173,7 @@ int DoSubst(ParsePtr p, DynamicBuffer *dbuf, Trigger *t, TimeTrig const *tt, int if (func && check_subst_args(func, 1)) { snprintf(s, sizeof(s), "subst_ampm(%d)", ch); expr = (char const *) s; - r = EvalExpr(&expr, &v, NULL); + r = EvalExprRunDisabled(&expr, &v, NULL); if (r == OK) { if (!DoCoerce(STR_TYPE, &v)) { snprintf(mycpm, sizeof(mycpm), "%s", v.v.str); @@ -196,7 +196,7 @@ int DoSubst(ParsePtr p, DynamicBuffer *dbuf, Trigger *t, TimeTrig const *tt, int if (func && check_subst_args(func, 1)) { snprintf(s, sizeof(s), "subst_ordinal(%d)", d); expr = (char const *) s; - r = EvalExpr(&expr, &v, NULL); + r = EvalExprRunDisabled(&expr, &v, NULL); if (r == OK) { if (!DoCoerce(STR_TYPE, &v)) { snprintf(myplu, sizeof(myplu), "%s", v.v.str); @@ -360,7 +360,7 @@ int DoSubst(ParsePtr p, DynamicBuffer *dbuf, Trigger *t, TimeTrig const *tt, int snprintf(ss, sizeof(s) - (ss-s), "(%d,'%04d-%02d-%02d',%02d:%02d)", altmode ? 1 : 0, y, m+1, d, h, min); expr = (char const *) s; - r = EvalExpr(&expr, &v, NULL); + r = EvalExprRunDisabled(&expr, &v, NULL); if (r == OK) { if (!DoCoerce(STR_TYPE, &v)) { if (DBufPuts(dbuf, v.v.str) != OK) { @@ -383,7 +383,7 @@ int DoSubst(ParsePtr p, DynamicBuffer *dbuf, Trigger *t, TimeTrig const *tt, int snprintf(s, sizeof(s), "%s(%d,'%04d-%02d-%02d',%02d:%02d)", substname, altmode ? 1 : 0, y, m+1, d, h, min); expr = (char const *) s; - r = EvalExpr(&expr, &v, NULL); + r = EvalExprRunDisabled(&expr, &v, NULL); if (r == OK) { if (v.type != INT_TYPE || v.v.val != 0) { if (!DoCoerce(STR_TYPE, &v)) { @@ -439,7 +439,7 @@ int DoSubst(ParsePtr p, DynamicBuffer *dbuf, Trigger *t, TimeTrig const *tt, int snprintf(s, sizeof(s), "%s(%d,'%04d-%02d-%02d',%02d:%02d)", substname, altmode ? 1 : 0, y, m+1, d, h, min); expr = (char const *) s; - r = EvalExpr(&expr, &v, NULL); + r = EvalExprRunDisabled(&expr, &v, NULL); if (r == OK) { if (v.type != INT_TYPE || v.v.val != 0) { if (!DoCoerce(STR_TYPE, &v)) { diff --git a/src/expr.c b/src/expr.c index e6552a57..0d01fc94 100644 --- a/src/expr.c +++ b/src/expr.c @@ -694,6 +694,7 @@ eval_userfunc(expr_node *node, Value *locals, Value *ans, int *nonconst) Value *new_locals = NULL; expr_node *kid; int i, r, j, pushed; + int old_rundisabled; /* If we have <= STACK_ARGS_MAX, store them on the stack here */ Value stack_locals[STACK_ARGS_MAX]; @@ -781,9 +782,15 @@ eval_userfunc(expr_node *node, Value *locals, Value *ans, int *nonconst) DBG(debug_enter_userfunc(node, new_locals, f->nargs)); + old_rundisabled = RunDisabled; + if (f->run_disabled) { + RunDisabled |= RUN_UF; + } /* Evaluate the function's expr_node tree */ r = evaluate_expr_node(f->node, new_locals, ans, nonconst); + RunDisabled = old_rundisabled; + DBG(debug_exit_userfunc(node, ans, r, new_locals, f->nargs)); if (r != OK) { @@ -2869,6 +2876,24 @@ static char const *get_operator_name(expr_node *node) else return "UNKNOWN_OPERATOR"; } +/***************************************************************/ +/* */ +/* EvalExprRunDisabled - parse and evaluate an expression */ +/* Evaluate an expression. Return 0 if OK, non-zero if error */ +/* Put the result into value pointed to by v. During */ +/* evaluation, RUN will be disabled */ +/* */ +/***************************************************************/ +int EvalExprRunDisabled(char const **e, Value *v, ParsePtr p) +{ + int old_run_disabled = RunDisabled; + int r; + RunDisabled |= RUN_CB; + r = EvalExpr(e, v, p); + RunDisabled = old_run_disabled; + return r; +} + /***************************************************************/ /* */ /* EvalExpr - parse and evaluate an expression. */ diff --git a/src/funcs.c b/src/funcs.c index 669e882b..b22a8289 100644 --- a/src/funcs.c +++ b/src/funcs.c @@ -1170,6 +1170,7 @@ static int FOrd(func_info *info) char const *e = buf; Value val; int nonconst; + int old_rundisabled; val.type = ERR_TYPE; snprintf(buf, sizeof(buf), "ordx(%d)", ARGV(0)); @@ -1178,7 +1179,10 @@ static int FOrd(func_info *info) return r; } in_ford = 1; + old_rundisabled = RunDisabled; + RunDisabled |= RUN_CB; r = evaluate_expr_node(n, NULL, &val, &nonconst); + RunDisabled = old_rundisabled; in_ford = 0; free_expr_tree(n); if (r != OK) { @@ -4156,7 +4160,7 @@ FEval(func_info *info) { expr_node *n; int r; - int run_was_enabled = 0; + int old_run_disabled; ASSERT_TYPE(0, STR_TYPE); char const *e = ARGSTR(0); @@ -4168,14 +4172,13 @@ FEval(func_info *info) } /* Disable shell() command in eval */ - if (! (RunDisabled & RUN_IN_EVAL)) { - run_was_enabled = 1; - RunDisabled |= RUN_IN_EVAL; - } + old_run_disabled = RunDisabled; + RunDisabled |= RUN_IN_EVAL; + r = evaluate_expr_node(n, NULL, &(info->retval), &(info->nonconst)); - if (run_was_enabled) { - RunDisabled &= ~RUN_IN_EVAL; - } + + RunDisabled = old_run_disabled; + free_expr_tree(n); return r; } diff --git a/src/protos.h b/src/protos.h index 6d2566eb..348c18b0 100644 --- a/src/protos.h +++ b/src/protos.h @@ -66,6 +66,7 @@ void unlimit_execution_time(void); expr_node *free_expr_tree(expr_node *node); expr_node *clone_expr_tree(expr_node const *node, int *r); int EvalExpr (char const **e, Value *v, ParsePtr p); +int EvalExprRunDisabled(char const **e, Value *v, ParsePtr p); int DoCoerce (char type, Value *v); char const *PrintValue (Value *v, FILE *fp); int CopyValue (Value *dest, const Value *src); diff --git a/src/queue.c b/src/queue.c index d23df402..5cf8af80 100644 --- a/src/queue.c +++ b/src/queue.c @@ -640,13 +640,15 @@ static int CalculateNextTimeUsingSched(QueuedRem *q) return NO_TIME; } - RunDisabled = q->RunDisabled; /* Don't want weird scheduling functions - to be a security hole! */ while(1) { char exprBuf[VAR_NAME_LEN+32]; snprintf(exprBuf, sizeof(exprBuf), "%s(%d)", q->sched, q->ntrig); s = exprBuf; - r = EvalExpr(&s, &v, NULL); + if (q->RunDisabled) { + r = EvalExprRunDisabled(&s, &v, NULL); + } else { + r = EvalExpr(&s, &v, NULL); + } if (r) { q->sched[0] = 0; return NO_TIME; diff --git a/src/types.h b/src/types.h index ace10a32..3c20270f 100644 --- a/src/types.h +++ b/src/types.h @@ -263,6 +263,8 @@ typedef struct { #define RUN_SCRIPT 0x02 #define RUN_NOTOWNER 0x04 #define RUN_IN_EVAL 0x08 +#define RUN_UF 0x10 /* A user-function defined with RUN OFF */ +#define RUN_CB 0x20 /* A callback */ /* Flags for the SimpleCalendar format */ #define SC_AMPM 0 /* Time shown as 3:00am, etc. */ @@ -317,6 +319,7 @@ typedef struct udf_struct { int lineno_start; int recurse_flag; int been_pushed; + int run_disabled; } UserFunc; /* A pushed systtem variable */ diff --git a/src/userfns.c b/src/userfns.c index bcdf9532..792dd3b4 100644 --- a/src/userfns.c +++ b/src/userfns.c @@ -275,6 +275,11 @@ int DoFset(ParsePtr p) func->lineno_start = LineNoStart; func->recurse_flag = 0; func->been_pushed = 0; + if (RunDisabled) { + func->run_disabled = 1; + } else { + func->run_disabled = 0; + } if (in_constant_context()) { func->is_constant = 1; } else { diff --git a/tests/safety.rem b/tests/safety.rem new file mode 100644 index 00000000..9103c2d8 --- /dev/null +++ b/tests/safety.rem @@ -0,0 +1,31 @@ +BANNER % +SET $AddBlankLines 0 + +FSET danger(x) shell("echo oops") + +RUN OFF +FSET safe(x) shell("echo nope") +FSET safe2(x) danger(x) +RUN ON + +DEBUG +x +set a danger(1) +set b safe(1) +set b safe2(1) + +RUN OFF +set a danger(1) +set b safe(1) +set b safe2(1) + +RUN ON + +DEBUG -x +FSET subst_b(a, b, c) shell("echo nooooo....") + +REM MSG [subst_b(1, 2, 3)] +FLUSH +REM MSG %b +FLUSH +REM MSG [subst_b(1, 2, 3)] +FLUSH \ No newline at end of file diff --git a/tests/test-rem b/tests/test-rem index 4b1a7593..f9fa50ec 100644 --- a/tests/test-rem +++ b/tests/test-rem @@ -621,12 +621,17 @@ rm -f ../tests/once.timestamp grep -F -v '$SysInclude' < ../tests/test.out > ../tests/test.out.1 && mv -f ../tests/test.out.1 ../tests/test.out # If "man" accepts the --warnings flag, test all the man pages. +RUNMAN=0 man man | grep -e --warnings > /dev/null 2>&1 -if test $? = 0 ; then - for i in ../man/*.1 ; do - man --warnings=w $i 2>>../tests/test.out 1>/dev/null - done +if test "$?" = 0 ; then + RUNMAN=1 fi +for i in ../man/*.1 ; do + echo "Checking $i..." >> ../tests/test.out + if test "$RUNMAN" = 1 ; then + man --warnings=w $i 2>>../tests/test.out 1>/dev/null + fi +done # Test --print-tokens long option ../src/remind --print-tokens < /dev/null >> ../tests/test.out 2>&1 @@ -788,6 +793,8 @@ REM TODO 2025-08-13 COMPLETE-THROUGH 2025-08-13 MSG %(LANGID) Task3%: EOF done +../src/remind -q ../tests/safety.rem 2025-08-13 >> ../tests/test.out 2>&1 + cmp -s ../tests/test.out ../tests/test.cmp if [ "$?" = "0" ]; then echo "Remind: Acceptance test PASSED" diff --git a/tests/test.cmp b/tests/test.cmp index a9a26b80..101af8a3 100644 --- a/tests/test.cmp +++ b/tests/test.cmp @@ -24392,6 +24392,10 @@ with_time foo bar +Checking ../man/rem.1... +Checking ../man/rem2ps.1... +Checking ../man/remind.1... +Checking ../man/tkremind.1... # Remind Tokens addomit @@ -39678,3 +39682,44 @@ This is executed by the shell. 2025/08/13 * * * * ro Task1 2025/08/13 * * * * ro Task2 2025/08/13 * * * * ro Task3 (finalizată) +Entering UserFN danger(1) +shell("echo oops") => "oops" +Leaving UserFN danger(1) => "oops" +Entering UserFN safe(1) +shell("echo nope") => RUN disabled +../tests/safety.rem(13): shell(): RUN disabled + ../tests/safety.rem(7): [#0] In function `safe' +Leaving UserFN safe(1) => RUN disabled +Entering UserFN safe2(1) +x => 1 +Entering UserFN danger(1) +shell("echo oops") => RUN disabled +../tests/safety.rem(14): shell(): RUN disabled + ../tests/safety.rem(4): [#0] In function `danger' + ../tests/safety.rem(8): [#1] Called from function `safe2' +Leaving UserFN danger(1) => RUN disabled +Leaving UserFN safe2(1) => RUN disabled +Entering UserFN danger(1) +shell("echo oops") => RUN disabled +../tests/safety.rem(17): shell(): RUN disabled + ../tests/safety.rem(4): [#0] In function `danger' +Leaving UserFN danger(1) => RUN disabled +Entering UserFN safe(1) +shell("echo nope") => RUN disabled +../tests/safety.rem(18): shell(): RUN disabled + ../tests/safety.rem(7): [#0] In function `safe' +Leaving UserFN safe(1) => RUN disabled +Entering UserFN safe2(1) +x => 1 +Entering UserFN danger(1) +shell("echo oops") => RUN disabled +../tests/safety.rem(19): shell(): RUN disabled + ../tests/safety.rem(4): [#0] In function `danger' + ../tests/safety.rem(8): [#1] Called from function `safe2' +Leaving UserFN danger(1) => RUN disabled +Leaving UserFN safe2(1) => RUN disabled +nooooo.... +../tests/safety.rem(28): shell(): RUN disabled + ../tests/safety.rem(24): [#0] In function `subst_b' +today +nooooo....