]> rtime.felk.cvut.cz Git - novaboot.git/blob - server/novaboot-shell
6c7225048ab7316531f0d01fd5a4ac34daa86c75
[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         if [ "$1" = "on" ]; then systemctl --user start novaboot-delayed-power-off.service; fi
103     else
104         eval "$cmd"
105     fi
106 }
107
108 run_console() {
109     trap "rm -f $RUN_DIR/ppid" EXIT
110     echo $NOVABOOT_PPID > $RUN_DIR/ppid
111     echo 'novaboot-shell: Connected'
112     # TODO: $reset_begin_cmd
113     [ -n "${on_cmd}" ] && power on
114     eval "$1"
115 }
116
117 # Run novaboot with the same configuration as specified in
118 # ~/.novaboot-shell, but allow the caller to extend of override them
119 # via parameters of this function.
120 run_novaboot() {
121     nbscript=$1
122     shift
123     OLD_IFS=$IFS
124     # Split $target_config below by newlines, not by words
125     IFS="
126 "
127     novaboot "$nbscript" $target_config --server="$HOME/tftproot" --reset-cmd="${reset_cmd:?}" --remote-cmd="${console_cmd:?}" "$@"
128     IFS=$OLD_IFS
129 }
130
131 # run_subcommand should be called only after permission checks and/or locking
132 run_subcommand() {
133     read_config
134     case "$*" in
135         "default")
136             run_console "${default_cmd:-${console_cmd:?}}";;
137         "console")
138             run_console "${console_cmd:?}";;
139         "reset")
140             eval "${reset_cmd:?}";;
141         "rsync --server "*" . .")
142             if ! [ $# -eq 5 -o \( $# -eq 6 -a "$4" = '--log-format=X' \) ]; then
143                 die "Unexpected rsync invocation: $*"
144             fi
145             mkdir -p "$HOME/tftproot"
146             cd "$HOME/tftproot"
147             exec "$@";;
148         "on")
149             power on
150             exit;;
151         "off")
152             power off
153             exit;;
154         *)
155             die "Unknown command: $*";;
156     esac
157 }
158
159 main() {
160     if [ "$1" = "-c" ]; then
161         set -- $2
162     elif [ $# -gt 0 ]; then
163         die "Permission denied"
164     fi
165
166     NB_ADMIN=
167     if [ "$1" = "user" ]; then
168         # Get user name encoded in ~/.ssh/authorized_keys
169         export NB_USER="$2";
170         [ "$3" = "admin" ] && NB_ADMIN=1
171         set -- $SSH_ORIGINAL_COMMAND
172     fi
173
174     IP=${SSH_CONNECTION%% *}
175     if [ "$IP" ]; then
176         HOST=$(getent hosts $IP) || HOST=$IP
177     else
178         HOST=localhost
179     fi
180     REMOTE=${HOST##* }
181     DATE=$(LANG=C date +'%F_%T')
182     export NOVABOOT_ID="${NB_USER:-?} $DATE ${REMOTE}"
183     export NOVABOOT_PPID=$PPID
184
185     mkdir -p "$RUN_DIR"
186
187     case "$1" in
188         # Commands allowed at any time
189         "") locked $0 default;;
190         "console") locked $0 console;;
191         "get-config") read_config && echo -n "${target_config}"; exit;;
192         "add-key") shift; add_key "$@"; exit;;
193         "shell") exec_shell; exit;;
194         "help") print_help;;
195
196         # Commands allowed only when nobody or the same user is connected
197         # to the console. "The same user" means that we were executed by
198         # the same sshd process that has the lock. This is ensured by
199         # using SSH connection sharing on client side.
200         reset | rsync | on | off)
201             ALLOWED_PPID=$(cat $RUN_DIR/ppid 2>/dev/null || :)
202             if [ "$PPID" -eq "${ALLOWED_PPID:-0}" ]; then run=unlocked; else run=locked; fi
203             $run $0 "$@";;
204         *)
205             echo >&2 "novaboot-shell: Command not allowed: $*"
206             logger -p error "novaboot-shell: Command not allowed: $*"
207             exit 1;;
208     esac
209 }
210
211 if [ -d "$HOME" ]; then
212     RUN_DIR="$HOME"
213 else
214     RUN_DIR="/tmp/novaboot-shell@$USER"
215     mkdir -p "$RUN_DIR"
216 fi
217
218 if [ -z "$NOVABOOT_ID" ] && [ "$PPID" -ne 1 ]; then
219     main "$@"
220 else
221     run_subcommand "$@"
222 fi