-
Notifications
You must be signed in to change notification settings - Fork 0
/
clicmd
executable file
·272 lines (237 loc) · 7.15 KB
/
clicmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#!/bin/bash -eu
#
# YADRO OpenBMC Command Line Interface
# Copyright (C) 2020-2021 YADRO
#
# SPDX-License-Identifier: Apache-2.0
#
# Base CLI command handler.
# This script is used for setting up the environment, elevating privileges
# and handling hierarchical commands.
# prevent modification of executable path
PATH="/bin:/sbin:/usr/bin:/usr/sbin"
# Role related definitions
ROLE_ADMIN="admin"
ROLE_OPERATOR="operator"
ROLE_USER="user"
GROUP_ADMIN="%GROUP_ADMIN%"
GROUP_OPERATOR="%GROUP_OPERATOR%"
GROUP_USER="%GROUP_USER%"
# If we are running escalated, we have GROUP already set by the caller
if [[ "${USER}" != "root" || ! -v GROUP ]]; then
if [[ "${USER}" = "root" ]]; then
echo "Can't run CLI commands as root directly, use an admin account" >&2
exit 2
fi
GROUP=$(id -Gn | sed -n 's/.*\(%GROUP_REGEX%\)[ ]*.*$/\1/gp')
fi
if [[ -z "${GROUP}" ]]; then
echo "Can't find privilege group for user ${USER}" >&2
exit 2
fi
# Lookup for group by role name (privilege in BMC terms)
function role_to_group {
local role="${1:-}"
case "${role}" in
"${ROLE_ADMIN}") echo "${GROUP_ADMIN}";;
"${ROLE_OPERATOR}") echo "${GROUP_OPERATOR}";;
"${ROLE_USER}") echo "${GROUP_USER}";;
*)
echo "Invalid role name: ${role}" >&2
return 1
;;
esac
}
# Lookup for role (privilege in BMC terms) by group name
function group_to_role {
local grp="${1:-}"
case "${grp}" in
"${GROUP_ADMIN}") echo "${ROLE_ADMIN}";;
"${GROUP_OPERATOR}") echo "${ROLE_OPERATOR}";;
"${GROUP_USER}") echo "${ROLE_USER}";;
*)
echo "Invalid group name: ${grp}" >&2
return 1
;;
esac
}
ROLE=$(group_to_role "${GROUP}")
# command to execute
USER_CMD="${BASH_SOURCE[0]}"
REAL_CMD="/usr/share/cli/$(basename "${USER_CMD}")"
if [[ ! -f "${REAL_CMD}" ]]; then
echo "Invalid CLI command: ${USER_CMD}" >&2
exit 2
fi
# Execute an extenal tool and replace the program name
# by the name of the CLI command that called this function
function exec_tool {
local FNAME="${FUNCNAME[1]##cmd_}"
local PNAME
PNAME="$(basename "${USER_CMD}") ${FNAME//_/ }"
local TOOL="$1"; shift
exec -a "${PNAME}" "${TOOL}" "$@"
}
# Handle subcommand that requires additional arguments.
function subcommand {
local parent="${FUNCNAME[1]}"
local -a children
# This keeps shellcheck happy yet never fails. We're fine with that here.
mapfile -t children < <(declare -F | sed -n "/${parent}_[^_]*$/s/.* //p")
if [[ $# -eq 0 ]] || [[ $1 =~ ^-*h(elp)?$ ]]; then
# print help as a list of subcommands at the current layer
print_help "${parent}" | head -n 1
local cmd
for cmd in "${children[@]}"; do
local help
help=$(print_help "${cmd}" | head -n 1)
[[ -n "${help}" ]] && printf "%-16s %s\n" "${cmd#"${parent}"_}" "${help}"
done
return
fi
# check subcommand for existence
local sub="$1"
shift
local cmd="${parent}_${sub}"
# Double quotes around rhs are intentional, cmd doesn't contain regexps.
# shellcheck disable=SC2076
if [[ ! " ${children[*]} " =~ " ${cmd} " ]]; then
echo "Invalid argument: ${sub}" >&2
return 1
fi
# handle help request for command, if command does not have own help info
if [[ $# -ne 0 ]] && [[ $1 =~ ^-*h(elp)?$ ]] && ! declare -F | grep -q "${cmd}_[^_]*$"; then
if [[ $(print_help "${cmd}" | wc -l) -gt 1 ]]; then
print_help "${cmd}"
return
fi
fi
# if command has @sudo tag - we need to run the function with elevated privileges
if grep -q "^#\\s*@sudo\\s\\+${cmd}\\s" "${REAL_CMD}" && [[ ${EUID} -ne 0 ]]; then
local sub_path="${cmd/#cmd_}"
# shellcheck disable=SC2086
GROUP=${GROUP} exec sudo "${USER_CMD}" ${sub_path//_/ } "$@"
fi
# execute subcommand
${cmd} "$@"
}
# Print help for specified command.
function print_help {
local user_cmd=${1}
awk "
# Clear match on non-comment lines
/^[^#]/ { matched = 0; restricted = 0; next }
# All comment blocks with @restrict are checked against the current role
# and marked as matching if needed
/^#\\s+@restrict\\s+${user_cmd}\\s+/ {
restricted = 1
split(\$4,roles,\",\")
for (i = 0; i < length(roles); i++) {
if (roles[i] == \"${ROLE}\") {
matched = 1
}
}
next
}
# Any comment line after @doc for the command must be printed
# if there was no @restrict in this comment block
/^#\\s+@doc\\s+${user_cmd}\\s*$/ {
if (!restricted) {
matched = 1
}
next
}
# Print the matching line, cut off the leading comment symbols
/^#/ { if (matched) print substr(\$0,3) }
" < "${REAL_CMD}"
}
# Bash auto completion support: print subcommands
function autocomplete {
local parent="cmd"
while [[ $# -gt 0 ]]; do
# Only alphanumeric subcommands are possible
[[ $1 =~ ^[[:alnum:]]+$ ]] || break
parent+="_$1"
shift
done
declare -F | sed -n "/${parent}_[^_]*$/s/.* ${parent}_//p"
}
# Ask user for confirmation.
function confirm {
local yn
# We don't want to accept `\yes` as `yes`, hence the `-r`
read -r -p "Are you sure? (Type 'yes' to continue) " yn
if [[ ${yn} != yes ]]; then
echo "Aborted by user" >&2
return 1
fi
}
# Abort execution because of invalid argument.
function abort_badarg {
echo "Invalid argument: ${1:-?}" >&2
return 1
}
# Abort execution if at least one argument exists
function expect_noarg {
if [[ $# -ne 0 ]]; then
echo "Unexpected argument: $1" >&2
return 1
fi
}
# Top level command handler.
function cmd {
subcommand "$@"
}
# @brief log audit message
# @param message
function log_audit()
{
echo "${SUDO_USER:-${USER}}" "$@" | systemd-cat -t CLI
}
# Only import the functions allowed for the current role
# Non-constant source is intentional here. The source is verified
# separately, so tell shellcheck to accept this.
# shellcheck disable=SC1090
source <(awk -vrole="${ROLE}" -vcmd="$(basename "${USER_CMD}")" '
# Initially assume everything is allowed
BEGIN { restricted = 0 }
# Mimic a not found command if it is restricted for the user
/^# Restrict:\s+/ {
prohibited=1
split($3,roles,",")
for (i = 0; i < length(roles); i++) {
if (roles[i] == role) {
# If the role matches, allow the function
prohibited = 0
}
}
if (prohibited) {
printf("echo \"-sh: %s: command not found\"\nexit 1\n", cmd)
exit 1
}
}
/^#\s+@restrict\s+[^\s]+/ {
# If there is @restrict, assume the function is not allowed
restricted = 1
split($4,roles,",")
for (i = 0; i < length(roles); i++) {
if (roles[i] == role) {
# If the role matches, allow the function
restricted = 0
}
}
}
{
if (!restricted) print
}
# End of top-level block means end of restricted function
/^\}/ { restricted = 0; }
' "${REAL_CMD}")
if [[ $# -ne 0 ]] && [[ $1 == autocomplete ]]; then
# Special case - request for bash autocompletion
shift
autocomplete "$@"
else
# Redirect processing to the top level handler
cmd "$@"
fi