]> rtime.felk.cvut.cz Git - novaboot.git/blob - server/novaboot-shell
a807cf8a1704b981b7803b60fede90e7604a8a46
[novaboot.git] / server / novaboot-shell
1 #!/bin/sh
2
3 set -e
4
5 die() {
6     echo >&2 "novaboot-shell: $*"
7     exit 1
8 }
9
10 print_help() {
11     cat <<EOF
12 Target commands:
13 - console (default command)
14 - reset
15 - on
16 - off
17 - rsync ...
18 - get-config
19
20 Management commands:
21 - help
22 EOF
23
24     if [ "$NB_ADMIN" ]; then
25         cat <<EOF
26 - add-key
27 - shell (use with ssh -t)
28 EOF
29     fi
30     exit 0
31 }
32
33 add_key() {
34     local user
35     [ "$NB_ADMIN" ] || return 1
36
37     case $# in
38         0) die "Usage: ssh ... add-key USERNAME < id_rsa.pub";;
39         1) break;;
40         *) die "User name must not contain spaces: $*";;
41     esac
42     user="$1"
43     key=$(cat)
44
45     tmp=$(mktemp ~/.ssh/authorized_keys.XXXXXXXX)
46     {
47         cat ~/.ssh/authorized_keys
48         echo "command=\"user $user\" $key"
49     } | sort -u > $tmp
50
51     mv $tmp ~/.ssh/authorized_keys
52 }
53
54 exec_shell() {
55     [ "$NB_ADMIN" ] || die "Permission denied"
56     if ! tty > /dev/null; then
57         echo "novaboot-shell: Consider starting the shell with 'ssh -t'"
58     fi
59     exec /bin/bash || exec /bin/sh
60 }
61
62 lock_queue() {
63     lslocks | awk '{ if ($9 == "'"$RUN_DIR"'") { print $2 } }'
64 }
65
66 print_queue() {
67     local queue
68
69     queue=$(
70         for pid in $(lock_queue); do
71             echo $pid $(sed --null-data -ne '/^NOVABOOT_ID=/ s///p' /proc/$pid/environ)
72         done | sort)
73     if [ "$queue" ]; then
74         echo "Target is occupied by:"
75         ( echo "PID USER LOGIN_TIME FROM"; echo "$queue" ) | column -t
76     fi
77 }
78
79 locked() {
80     print_queue
81     exec flock --no-fork "$RUN_DIR" "$@"
82 }
83
84 unlocked() {
85     exec "$@"
86 }
87
88 read_config() {
89     . "${NOVABOOT_SHELL_CONFIG:-$HOME/.novaboot-shell}"
90 }
91
92 power() {
93     local cmd
94     case "$1" in
95         "on")  cmd="${on_cmd:?}";;
96         "off") cmd="${off_cmd:?}";;
97         *) die "Unexpected power parameter";;
98     esac
99
100     if [ "$PPID" -ne 1 ] && systemctl --user is-enabled --quiet novaboot-delayed-power-off.service; then
101         sudo novaboot-power "$1"
102     else
103         eval "$cmd"
104     fi
105 }
106
107 # run_subcommand should be called only after permission checks and/or locking
108 run_subcommand() {
109     read_config
110     case "$*" in
111         "console")
112             trap "rm -f $RUN_DIR/ppid" EXIT
113             echo $NOVABOOT_PPID > $RUN_DIR/ppid
114             echo 'novaboot-shell: Connected'
115             # TODO: $reset_begin_cmd
116             [ -n "${on_cmd}" ] && power on
117             eval exec "${console_cmd:?}";;
118         "reset")
119             eval exec "${reset_cmd:?}";;
120         "rsync --server "*" . .")
121             if ! [ $# -eq 5 -o \( $# -eq 6 -a "$4" = '--log-format=X' \) ]; then
122                 die "Unexpected rsync invocation: $*"
123             fi
124             mkdir -p "$HOME/tftproot"
125             cd "$HOME/tftproot"
126             exec "$@";;
127         "on")
128             power on
129             exit;;
130         "off")
131             power off
132             exit;;
133         *)
134             die "Unknown command: $*";;
135     esac
136 }
137
138 main() {
139     if [ "$1" = "-c" ]; then
140         set -- $2
141     elif [ $# -gt 0 ]; then
142         die "Permission denied"
143     fi
144
145     NB_ADMIN=
146     if [ "$1" = "user" ]; then
147         # Get user name encoded in ~/.ssh/authorized_keys
148         export NB_USER="$2";
149         [ "$3" = "admin" ] && NB_ADMIN=1
150         set -- $SSH_ORIGINAL_COMMAND
151     fi
152
153     IP=${SSH_CONNECTION%% *}
154     if [ "$IP" ]; then
155         HOST=$(getent hosts $IP) || HOST=$IP
156     else
157         HOST=localhost
158     fi
159     REMOTE=${HOST##* }
160     DATE=$(LANG=C date +'%F_%T')
161     export NOVABOOT_ID="${NB_USER:-?} $DATE ${REMOTE}"
162     export NOVABOOT_PPID=$PPID
163
164     mkdir -p "$RUN_DIR"
165
166     case "$1" in
167         # Commands allowed at any time
168         "console"|"") locked $0 console;;
169         "get-config") read_config && echo -n "${target_config}"; exit;;
170         "add-key") shift; add_key "$@"; exit;;
171         "shell") exec_shell; exit;;
172         "help") print_help;;
173
174         # Commands allowed only when nobody or the same user is connected
175         # to the console. "The same user" means that we were executed by
176         # the same sshd process that has the lock. This is ensured by
177         # using SSH connection sharing on client side.
178         reset | rsync | on | off)
179             ALLOWED_PPID=$(cat $RUN_DIR/ppid 2>/dev/null || :)
180             if [ "$PPID" -eq "${ALLOWED_PPID:-0}" ]; then run=unlocked; else run=locked; fi
181             $run $0 "$@";;
182         *)
183             echo >&2 "novaboot-shell: Command not allowed: $*"
184             logger -p error "novaboot-shell: Command not allowed: $*"
185             exit 1;;
186     esac
187 }
188
189 if [ -d "$HOME" ]; then
190     RUN_DIR="$HOME"
191 else
192     RUN_DIR="/tmp/novaboot-shell@$USER"
193     mkdir -p "$RUN_DIR"
194 fi
195
196 if [ -z "$NOVABOOT_ID" ] && [ "$PPID" -ne 1 ]; then
197     main "$@"
198 else
199     run_subcommand "$@"
200 fi
201 exit
202
203 : <<EOF
204 =encoding utf8
205
206 =head1 NAME
207
208 novaboot-shell - provides novaboot with unified SSH-based interface for controlling target hardware
209
210 =head1 SYNOPSIS
211
212 B<novaboot-shell> -c "[command [arguments...]]"
213
214 B<novaboot-shell> [command [arguments...]]
215
216 B<ssh target@server> [command [arguments...]]
217
218 =head1 DESCRIPTION
219
220 B<novaboot-shell> provides L<novaboot(1)> with a unified SSH-based
221 interface for controlling the target hardware. This simplifies
222 client-side configuration, because clients typically need only the
223 I<--ssh=...> option. B<novaboot-shell> is typically configured as a
224 login shell of special user accounts associated with the target
225 hardware (as set by L<adduser-novaboot(8)>). It ensures that users can
226 perform only a limited set of actions (see L</COMMANDS> below) with
227 the target and have no shell access on the server.
228
229 =head1 COMMANDS
230
231 =over 8
232
233 =item console
234
235 Connect to target console (usually serial line). When somebody is
236 connected to the console, other users are blocked from controlling the
237 target. Blocked users see a message indicating who blocks them.
238
239 The user connected to the console is able to invoke other commands
240 such as L</reset>, but only when the command is invoked via the same
241 SSH connection. This can be accomplished by using SSH connection
242 sharing, which is what L<novaboot(1)> uses (see I<-M> and I<-S> in
243 L<ssh(1)>).
244
245 This is the default command when no command is specified on command
246 line.
247
248 =item reset
249
250 Reset the target hardware.
251
252 =item on
253
254 Power on the target hardware.
255
256 =item off
257
258 Power off the target hardware.
259
260 =item rsync [...]
261
262 This command is not meant to be invoked directly by the user. It
263 allows using L<rsync(1)> to copy files to the target, perhaps for TFTP
264 server. The rsync command must be invoked as: C<rsync ...
265 target@server:>, i.e. without specifying destination path. The files
266 will be stored into I<$HOME/tftproot>.
267
268 =item user <uernamename> [admin]
269
270 User command is meant to be used with C<command=> option in SSH's
271 L<authorized_keys(5)> file. It allows the shell to display
272 human-readable names when printing information about who blocks the
273 target. Then, the real command is taken from SSH_ORIGINAL_COMMAND
274 environment variable.
275
276 When "admin" is specified after the user name, this user is considered
277 an administrator and is allowed to run L</add-key> and L</shell>
278 commands.
279
280 =item get-config
281
282 Prints novaboot configuration options needed for the target. One
283 option per line.
284
285 =back
286
287 =head2 Administration commands
288
289 Only administrators (see L</user>) are allowed to execute these
290 commands.
291
292 =over 8
293
294 =item add-key <username>
295
296 Reads the SSH public key from standard input and adds it into in
297 F<~/.ssh/authorized_keys>.
298
299 Example: C<ssh target@server add-key johndoe < john_rsa.pub>
300
301 =item shell
302
303 Runs shell on the server. Useful for editing configuration file. It is
304 better used with allocated pseudo-terminal.
305
306 Example: C<ssh -t target@server shell>
307
308 =back
309
310 =head1 CONFIGURATION FILE
311
312 B<novaboot-shell> reads configuration file from
313 F<$HOME/.novaboot-shell>. It should define values for the following
314 variables in the SH syntax.
315
316 =over 8
317
318 =item console_cmd
319
320 Command to C<exec> that connects to target's console.
321
322 =item reset_cmd
323
324 Command to C<exec> that resets the Target.
325
326 =item on_cmd
327
328 Command to C<exec> that powers the target on.
329
330 =item off_cmd
331
332 Command to C<exec> that powers the target off.
333
334 =item target_config
335
336 Novaboot command line options that specify which boot loader is used
337 by the target (L<novaboot(1)> rejects other, possibly dangerous, options).
338 Each option is on its own line and no quoting, escaping or stripping
339 is performed on the values.
340
341 Example:
342
343   target_config="\
344   --uboot=(uboot)
345   --uboot-init=setenv serverip 192.168.1.1; setenv ipaddr 192.168.1.10
346   --uboot-addr=kernel=0x8100000
347   --uboot-addr=fdt=0x83000000
348   --uboot-addr=ramdisk=0x83100000
349   "
350
351
352 =back
353
354 =head1 AUTHORS
355
356 Michal Sojka <sojkam1@fel.cvut.cz>
357
358 Latest version can be found at
359 L<https://github.com/wentasah/novaboot>.
360
361 =cut
362 EOF