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