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