]> rtime.felk.cvut.cz Git - novaboot.git/blob - server/novaboot-shell
server: Ensure that configuration can depend on logged-in user name
[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 RUN_DIR="$HOME"
170
171 if [ -z "$NOVABOOT_ID" ]; then
172     main "$@"
173 else
174     run_subcommand "$@"
175 fi
176 exit
177
178 : <<EOF
179 =encoding utf8
180
181 =head1 NAME
182
183 novaboot-shell - provides novaboot with unified SSH-based interface for controlling target hardware
184
185 =head1 SYNOPSIS
186
187 B<novaboot-shell> -c "[command [arguments...]]"
188
189 B<novaboot-shell> [command [arguments...]]
190
191 B<ssh target@server> [command [arguments...]]
192
193 =head1 DESCRIPTION
194
195 B<novaboot-shell> provides L<novaboot(1)> with a unified SSH-based
196 interface for controlling the target hardware. This simplifies
197 client-side configuration, because clients typically need only the
198 I<--ssh=...> option. B<novaboot-shell> is typically configured as a
199 login shell of special user accounts associated with the target
200 hardware (as set by L<adduser-novaboot(8)>). It ensures that users can
201 perform only a limited set of actions (see L</COMMANDS> below) with
202 the target and have no shell access on the server.
203
204 =head1 COMMANDS
205
206 =over 8
207
208 =item console
209
210 Connect to target console (usually serial line). When somebody is
211 connected to the console, other users are blocked from controlling the
212 target. Blocked users see a message indicating who blocks them.
213
214 The user connected to the console is able to invoke other commands
215 such as L</reset>, but only when the command is invoked via the same
216 SSH connection. This can be accomplished by using SSH connection
217 sharing, which is what L<novaboot(1)> uses (see I<-M> and I<-S> in
218 L<ssh(1)>).
219
220 This is the default command when no command is specified on command
221 line.
222
223 =item reset
224
225 Reset the target hardware.
226
227 =item on
228
229 Power on the target hardware.
230
231 =item off
232
233 Power off the target hardware.
234
235 =item rsync [...]
236
237 This command is not meant to be invoked directly by the user. It
238 allows using L<rsync(1)> to copy files to the target, perhaps for TFTP
239 server. The rsync command must be invoked as: C<rsync ...
240 target@server:>, i.e. without specifying destination path. The files
241 will be stored into I<$HOME/tftproot>.
242
243 =item user <uernamename> [admin]
244
245 User command is meant to be used with C<command=> option in SSH's
246 L<authorized_keys(5)> file. It allows the shell to display
247 human-readable names when printing information about who blocks the
248 target. Then, the real command is taken from SSH_ORIGINAL_COMMAND
249 environment variable.
250
251 When "admin" is specified after the user name, this user is considered
252 an administrator and is allowed to run L</add-key> and L</shell>
253 commands.
254
255 =item get-config
256
257 Prints novaboot configuration options needed for the target. One
258 option per line.
259
260 =back
261
262 =head2 Administration commands
263
264 Only administrators (see L</user>) are allowed to execute these
265 commands.
266
267 =over 8
268
269 =item add-key <username>
270
271 Reads the SSH public key from standard input and adds it into in
272 F<~/.ssh/authorized_keys>.
273
274 Example: C<ssh target@server add-key johndoe < john_rsa.pub>
275
276 =item shell
277
278 Runs shell on the server. Useful for editing configuration file. It is
279 better used with allocated pseudo-terminal.
280
281 Example: C<ssh -t target@server shell>
282
283 =back
284
285 =head1 CONFIGURATION FILE
286
287 B<novaboot-shell> reads configuration file from
288 F<$HOME/.novaboot-shell>. It should define values for the following
289 variables in the SH syntax.
290
291 =over 8
292
293 =item console_cmd
294
295 Command to C<exec> that connects to target's console.
296
297 =item reset_cmd
298
299 Command to C<exec> that resets the Target.
300
301 =item on_cmd
302
303 Command to C<exec> that powers the target on.
304
305 =item off_cmd
306
307 Command to C<exec> that powers the target off.
308
309 =item target_config
310
311 Novaboot command line options that specify which boot loader is used
312 by the target (L<novaboot(1)> rejects other, possibly dangerous, options).
313 Each option is on its own line and no quoting, escaping or stripping
314 is performed on the values.
315
316 Example:
317
318   target_config="\
319   --uboot=(uboot)
320   --uboot-init=setenv serverip 192.168.1.1; setenv ipaddr 192.168.1.10
321   --uboot-addr=kernel=0x8100000
322   --uboot-addr=fdt=0x83000000
323   --uboot-addr=ramdisk=0x83100000
324   "
325
326
327 =back
328
329 =head1 AUTHORS
330
331 Michal Sojka <sojkam1@fel.cvut.cz>
332
333 Latest version can be found at
334 L<https://github.com/wentasah/novaboot>.
335
336 =cut
337 EOF