]> rtime.felk.cvut.cz Git - novaboot.git/blob - server/novaboot-shell
server: Refactor console setup to a separate function
[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 /usr/bin/env 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_console() {
108     trap "rm -f $RUN_DIR/ppid" EXIT
109     echo $NOVABOOT_PPID > $RUN_DIR/ppid
110     echo 'novaboot-shell: Connected'
111     # TODO: $reset_begin_cmd
112     [ -n "${on_cmd}" ] && power on
113     eval "$1"
114 }
115
116 # run_subcommand should be called only after permission checks and/or locking
117 run_subcommand() {
118     read_config
119     case "$*" in
120         "console")
121             run_console "${console_cmd:?}";;
122         "reset")
123             eval exec "${reset_cmd:?}";;
124         "rsync --server "*" . .")
125             if ! [ $# -eq 5 -o \( $# -eq 6 -a "$4" = '--log-format=X' \) ]; then
126                 die "Unexpected rsync invocation: $*"
127             fi
128             mkdir -p "$HOME/tftproot"
129             cd "$HOME/tftproot"
130             exec "$@";;
131         "on")
132             power on
133             exit;;
134         "off")
135             power off
136             exit;;
137         *)
138             die "Unknown command: $*";;
139     esac
140 }
141
142 main() {
143     if [ "$1" = "-c" ]; then
144         set -- $2
145     elif [ $# -gt 0 ]; then
146         die "Permission denied"
147     fi
148
149     NB_ADMIN=
150     if [ "$1" = "user" ]; then
151         # Get user name encoded in ~/.ssh/authorized_keys
152         export NB_USER="$2";
153         [ "$3" = "admin" ] && NB_ADMIN=1
154         set -- $SSH_ORIGINAL_COMMAND
155     fi
156
157     IP=${SSH_CONNECTION%% *}
158     if [ "$IP" ]; then
159         HOST=$(getent hosts $IP) || HOST=$IP
160     else
161         HOST=localhost
162     fi
163     REMOTE=${HOST##* }
164     DATE=$(LANG=C date +'%F_%T')
165     export NOVABOOT_ID="${NB_USER:-?} $DATE ${REMOTE}"
166     export NOVABOOT_PPID=$PPID
167
168     mkdir -p "$RUN_DIR"
169
170     case "$1" in
171         # Commands allowed at any time
172         "console"|"") locked $0 console;;
173         "get-config") read_config && echo -n "${target_config}"; exit;;
174         "add-key") shift; add_key "$@"; exit;;
175         "shell") exec_shell; exit;;
176         "help") print_help;;
177
178         # Commands allowed only when nobody or the same user is connected
179         # to the console. "The same user" means that we were executed by
180         # the same sshd process that has the lock. This is ensured by
181         # using SSH connection sharing on client side.
182         reset | rsync | on | off)
183             ALLOWED_PPID=$(cat $RUN_DIR/ppid 2>/dev/null || :)
184             if [ "$PPID" -eq "${ALLOWED_PPID:-0}" ]; then run=unlocked; else run=locked; fi
185             $run $0 "$@";;
186         *)
187             echo >&2 "novaboot-shell: Command not allowed: $*"
188             logger -p error "novaboot-shell: Command not allowed: $*"
189             exit 1;;
190     esac
191 }
192
193 if [ -d "$HOME" ]; then
194     RUN_DIR="$HOME"
195 else
196     RUN_DIR="/tmp/novaboot-shell@$USER"
197     mkdir -p "$RUN_DIR"
198 fi
199
200 if [ -z "$NOVABOOT_ID" ] && [ "$PPID" -ne 1 ]; then
201     main "$@"
202 else
203     run_subcommand "$@"
204 fi
205 exit
206
207 : <<EOF
208 =encoding utf8
209
210 =head1 NAME
211
212 novaboot-shell - provides novaboot with unified SSH-based interface for controlling target hardware
213
214 =head1 SYNOPSIS
215
216 B<novaboot-shell> -c "[command [arguments...]]"
217
218 B<novaboot-shell> [command [arguments...]]
219
220 B<ssh target@server> [command [arguments...]]
221
222 =head1 DESCRIPTION
223
224 B<novaboot-shell> provides L<novaboot(1)> with a unified SSH-based
225 interface for controlling the target hardware. This simplifies
226 client-side configuration, because clients typically need only the
227 I<--ssh=...> option. B<novaboot-shell> is typically configured as a
228 login shell of special user accounts associated with the target
229 hardware (as set by L<adduser-novaboot(8)>). It ensures that users can
230 perform only a limited set of actions (see L</COMMANDS> below) with
231 the target and have no shell access on the server.
232
233 =head1 COMMANDS
234
235 =over 8
236
237 =item console
238
239 Connect to target console (usually serial line). When somebody is
240 connected to the console, other users are blocked from controlling the
241 target. Blocked users see a message indicating who blocks them.
242
243 The user connected to the console is able to invoke other commands
244 such as L</reset>, but only when the command is invoked via the same
245 SSH connection. This can be accomplished by using SSH connection
246 sharing, which is what L<novaboot(1)> uses (see I<-M> and I<-S> in
247 L<ssh(1)>).
248
249 This is the default command when no command is specified on command
250 line.
251
252 =item reset
253
254 Reset the target hardware.
255
256 =item on
257
258 Power on the target hardware.
259
260 =item off
261
262 Power off the target hardware.
263
264 =item rsync [...]
265
266 This command is not meant to be invoked directly by the user. It
267 allows using L<rsync(1)> to copy files to the target, perhaps for TFTP
268 server. The rsync command must be invoked as: C<rsync ...
269 target@server:>, i.e. without specifying destination path. The files
270 will be stored into I<$HOME/tftproot>.
271
272 =item user <uernamename> [admin]
273
274 User command is meant to be used with C<command=> option in SSH's
275 L<authorized_keys(5)> file. It allows the shell to display
276 human-readable names when printing information about who blocks the
277 target. Then, the real command is taken from SSH_ORIGINAL_COMMAND
278 environment variable.
279
280 When "admin" is specified after the user name, this user is considered
281 an administrator and is allowed to run L</add-key> and L</shell>
282 commands.
283
284 =item get-config
285
286 Prints novaboot configuration options needed for the target. One
287 option per line.
288
289 =back
290
291 =head2 Administration commands
292
293 Only administrators (see L</user>) are allowed to execute these
294 commands.
295
296 =over 8
297
298 =item add-key <username>
299
300 Reads the SSH public key from standard input and adds it into in
301 F<~/.ssh/authorized_keys>.
302
303 Example: C<ssh target@server add-key johndoe < john_rsa.pub>
304
305 =item shell
306
307 Runs shell on the server. Useful for editing configuration file. It is
308 better used with allocated pseudo-terminal.
309
310 Example: C<ssh -t target@server shell>
311
312 =back
313
314 =head1 CONFIGURATION FILE
315
316 B<novaboot-shell> reads configuration file from
317 F<$HOME/.novaboot-shell>. It should define values for the following
318 variables in the SH syntax.
319
320 =over 8
321
322 =item console_cmd
323
324 Command to C<exec> that connects to target's console.
325
326 Note that if you need more complex behaviour of the console command,
327 e.g., different behaviour for different users (distinguished by the
328 value of C<$NB_USER> variable), you can set this variable to a name of
329 a shell function, which you define in the configuration file and
330 implement the complex behaviour there.
331
332 =item reset_cmd
333
334 Command to C<exec> that resets the Target.
335
336 =item on_cmd
337
338 Command to C<exec> that powers the target on.
339
340 =item off_cmd
341
342 Command to C<exec> that powers the target off.
343
344 =item target_config
345
346 Novaboot command line options that specify which boot loader is used
347 by the target (L<novaboot(1)> rejects other, possibly dangerous, options).
348 Each option is on its own line and no quoting, escaping or stripping
349 is performed on the values.
350
351 Example:
352
353   target_config="\
354   --uboot=(uboot)
355   --uboot-init=setenv serverip 192.168.1.1; setenv ipaddr 192.168.1.10
356   --uboot-addr=kernel=0x8100000
357   --uboot-addr=fdt=0x83000000
358   --uboot-addr=ramdisk=0x83100000
359   "
360
361
362 =back
363
364 =head1 AUTHORS
365
366 Michal Sojka <sojkam1@fel.cvut.cz>
367
368 Latest version can be found at
369 L<https://github.com/wentasah/novaboot>.
370
371 =cut
372 EOF