xref: /freebsd/libexec/rc/safe_eval.sh (revision c4fde87a3236b500246d4fce76dbca2a3d64f73a)
1:
2# RCSid:
3#	$Id: safe_eval.sh,v 1.36 2026/10/13 22:44:09 sjg Exp $
4#
5#	@(#) Copyright (c) 2023-2026 Simon J. Gerraty
6#
7#	SPDX-License-Identifier: BSD-2-Clause
8#
9#	Please send copies of changes and bug-fixes to:
10#	sjg@crufty.net
11
12_SAFE_EVAL_SH=:
13
14# does local *actually* work?
15local_works() {
16    local _fu
17}
18
19if local_works > /dev/null 2>&1; then
20    _local=local
21else
22    _local=:
23fi
24
25##
26# safe_set_var ["options"] "var=val"
27#
28# Useful for processing command line input like 'var=val'.
29#
30# We perform a number of sanity checks similar to safe_set.
31# We set ssv_var=var and ssv_val=val so that caller can produce
32# error messages if desired.
33# To be safe, ssv_var and/or ssv_val will be set to 'invalid'
34# if they fail the relevant checks.
35#
36# Options:
37#
38# -a "allow"
39# 	list of allowed variable names.
40#
41# -o "only_chars"
42# 	white-list of acceptable chars in "val"
43# 	default is 'A-Za-z0-9_  ,/=:+-'.
44#
45# -v "values"
46#	list of allowed values.
47#
48# -x "extra_chars"
49# 	eXtra chars to add to white-list for "val".
50#
51# 1. if "var" contains anything but 'A-Za-z0-9_' or matches 'ssv_*'
52#    we set ssv_var=invalid and return 1.
53#
54# 2. if "val" contains anything not in the combined set of
55#    "only_chars" and "extra_chars" we will set ssv_val=invalid
56#    and return 2.
57#
58# 3. if "allow" is set and "var" is not a member we return 3.
59#
60# 4. if "values" is set and "val" is not a member we return 4.
61#
62# If we pass all that, we eval "var=val" and return 0.
63#
64safe_set_var() {
65    eval $_local ssv_allow ssv_extra ssv_only ssv_space ssv_rc ssv_values
66    # use [:space:] if we can as it includes \n
67    case " " in
68    [[:space:]]) ssv_space="[:space:]";;
69    *) ssv_space=" 	";;
70    esac
71    ssv_only="${ssv_space}A-Za-z0-9_.,/=:+\$-"
72    ssv_rc=
73
74    while :
75    do
76        : 1="$1"
77        case "$1" in
78        -a) ssv_allow="$2"; shift 2;;
79        -o) ssv_only="$2"; shift 2;;
80        -v) ssv_values="$2"; shift 2;;
81        -x) ssv_extra="$2"; shift 2;;
82        *) break;;
83        esac
84    done
85    if ${isPOSIX_SHELL:-false}; then
86        ssv_var="${1%%=*}"
87        ssv_val="${1#*=}"
88    else
89        ssv_var=`expr "$1" : '\([^=]*\)=.*'`
90        ssv_val=`expr "$1" : '[^=]*=\(.*\)'`
91    fi
92    : first is ssv_var sane?
93    case "$ssv_var" in
94    *[!A-Za-z0-9_]*|ssv_*)
95        ssv_var=invalid
96        ssv_rc=1
97        ;;
98    esac
99    : is ssv_val ok - "$ssv_val" match only [$ssv_extra$ssv_only]
100    case "$ssv_val" in
101    *[!$ssv_extra$ssv_only]*)
102        ssv_val=invalid
103        ssv_rc=${ssv_rc:-2}
104        ;;
105    esac
106    : is $ssv_var is allowed - "${2:-$ssv_var}"
107    case " ${ssv_allow:-$ssv_var} " in
108    *" $ssv_var "*) ;;
109    *) ssv_rc=${ssv_rc:-3};;
110    esac
111    case " ${ssv_values:-$ssv_val} " in
112    *" $ssv_val "*) ;;
113    *) ssv_rc=${ssv_rc:-4};;
114    esac
115    ssv_rc=${ssv_rc:-0}
116    if [ $ssv_rc = 0 ]; then
117        : this should be safe
118        eval "$ssv_var=\"$ssv_val\""
119    fi
120    return $ssv_rc
121}
122
123##
124# safe_set [xtras]
125#
126# return a safe variable setting
127# any non-alphanumeric chars other than those in "xtras"
128# will be replaced with '_'
129# Lines containing `` or $() are too likely to result in syntax errors
130# so just delete them.
131#
132# "xtras" should be used with caution and cannot include ';'
133#
134safe_set() {
135    ${SED:-sed} -e 's/^[ 	]*//;s/[ 	]*#.*//;s/^:.*//' \
136    -e '/`/d' -e '/\$(/d' \
137    -e '/^[A-Za-z_][A-Za-z0-9_]*=/!d;s;[^A-Za-z0-9_. 	"'"$1"'$,/=:+-];_;g;' \
138    -e '/=.*_.*[ 	]/s,=\(.*\),="\1",;s,"",",g'
139}
140
141##
142# safe_eval [file]
143#
144# eval variable assignments only from file
145# taking care to eliminate any shell meta chars
146#
147safe_eval() {
148    eval `cat "$@" | safe_set`
149}
150
151##
152# safe_eval_export [file]
153#
154# eval variable assignments only from file
155# taking care to eliminate any shell meta chars
156# export any variables thus set
157#
158safe_eval_export() {
159    eval `cat "$@" | safe_set | ${SED:-sed} 's/^\([^=]*\)=.*/&; export \1/'`
160}
161
162##
163# safe_dot file [...]
164#
165# feed all "file" that exist to safe_eval
166#
167safe_dot() {
168    eval $_local ef ex f rc
169    ef=
170    ex=
171    rc=1
172    while :
173    do
174        case "$1" in
175        --export) ex=_export; shift;;
176        *) break;;
177        esac
178    done
179    for f in "$@"
180    do
181        test -s "$f" -a -f "$f" || continue
182        : check for space or tab in "$f"
183        case "$f" in
184        *[[:space:]]*|*" "*|*"	"*) # we cannot do this efficiently
185            dotted="$dotted $f"
186            safe_eval$ex "$f"
187            rc=$?
188            continue
189            ;;
190        esac
191        ef="${ef:+$ef }$f"
192        dotted="$dotted $f"
193    done
194    test -z "$ef" && return $rc
195    safe_eval$ex $ef
196    return 0
197}
198
199# for testing
200case /$0 in
201*/safe_eval*)
202    case "$1" in
203    dot|eval|set) op=safe_$1; shift; $op "$@";;
204    set_var)
205        shift
206        allow=
207        extra=
208        only=
209        values=
210
211        while :
212        do
213            : 1="$1"
214            case "$1" in
215            -a) allow="$2"; shift 2;;
216            -o) only="$2"; shift 2;;
217            -v) values="$2"; shift 2;;
218            -x) extra="$2"; shift 2;;
219            *=*)
220                if safe_set_var ${allow:+-a "$allow"} \
221                    ${only:+-o "$only"} \
222                    ${values:+-v "$values"} \
223                    ${extra:+-x "$extra"} "$1"; then
224                    echo $ssv_var=$ssv_val
225                else
226                    case $? in
227                    1) echo invalid variable;;
228                    2) echo "invalid value for '$ssv_var'";;
229                    3) echo "denied variable '$ssv_var'";;
230                    4) echo "denied value '$ssv_val' for '$ssv_var'";;
231                    esac
232                fi
233                shift
234                ;;
235            *) break;;
236            esac
237        done
238        ;;
239    *)  safe_dot "$@";;
240    esac
241    ;;
242esac
243