/
split-browser
executable file
·141 lines (112 loc) · 4.69 KB
/
split-browser
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#!/bin/bash
set -e -u -o pipefail
shopt -s inherit_errexit nullglob
export -n IFS=$'\t'
# shellcheck disable=SC1090
for f in {,/usr/local}/etc/split-browser/*.bash; do source "$f"; done
export SB_CMD_INPUT="${SB_CMD_INPUT:-/run/split-browser/cmd-$$}"
# Parse command-line arguments
args=()
cli_prefs=()
for arg; do
case "$arg" in
-h|--help)
exec cat <<END
Usage: split-browser [--safest] [--pref=<line>...] [<argument>...]
--safest set Tor Browser's Security Level to Safest; equivalent to
--pref='pref("extensions.torbutton.security_slider", 1);'
--pref=<line> additional browser preference line, appended after those
stored in /etc/split-browser/prefs/*.js and
/usr/local/etc/split-browser/prefs/*.js
<argument> e.g. a URL; passed to the browser when starting (but not
when restarting due to Ctrl-Shift-u)
END
;;
--safest|--high) # --high is deprecated
cli_prefs+=( 'pref("extensions.torbutton.security_slider", 1);' )
;;
--pref=*)
cli_prefs+=( "${arg#*=}" )
;;
*)
args+=( "$arg" )
;;
esac
done
# When the DisposableVM sends us a request, the page URL and title fields are
# supplied in two versions each: printable ASCII, and UTF-8. Printable ASCII
# *URLs* use Punycode IDNs and percent-encoding. Printable ASCII *titles*
# actually come in as UTF-8 (normalized to NFKD, which tends to look better
# than the other normalization forms after all bytes outside of the printable
# ASCII range have been sanitized).
[[ ${SB_CHARSET-} == utf-8 ]] || SB_CHARSET=ascii
declare -A first_page_field=( [ascii]=2 [utf-8]=4 )
sanitize_ascii() { # replace any byte with _ but printable ASCII, tab, newline
LC_ALL=C stdbuf -oL tr -c '\040-\176\t\n' _
}
sanitize_utf-8() { # replace null byte with _; then validate as UTF-8
LC_ALL=C stdbuf -oL tr '\0' _ |
LC_ALL=C PYTHONIOENCODING=utf-8:strict \
python3 -Suc $'import sys\nfor line in sys.stdin: sys.stdout.write(line)'
}
# Launch via qubes.VMShell in the DisposableVM, because a straightforward
# 'qrexec-client-vm @dispvm split-browser-disp' call would require the user to
# manually create a policy in dom0. Transition to split-browser-disp with the
# trick described in /usr/lib/qubes/qrun-in-vm (not reused here, because it
# doesn't preserve exit status).
#
# Sanitize stdout and handle those requests (one per line), e.g. to get a login
# credential. The handler might then send commands into the input FIFO with
# split-browser-cmd. (This is essentially a crappy bidirectional RPC system
# implemented *inside* a qrexec RPC call's data streams. It would be much nicer
# to do multiple qrexec calls back and forth, but that requires lots of policy
# configuration for each persistent VM.) Also strictly sanitize stderr and
# distinguish it with a prefix.
dispvm() (
exec {saved_stdout_fd}>&1 >&2
trap 'rm -f -- "$SB_CMD_INPUT"{.tmp,}' EXIT
mkfifo -- "$SB_CMD_INPUT".tmp
exec {cmd_fd}<>"$SB_CMD_INPUT".tmp
d=/etc/qubes-rpc
bash_line="PATH=/usr/local$d:$d:\$PATH exec split-browser-disp"
printf '%s\n' "$bash_line" "$@" >&"$cmd_fd" # 64 KB pipe capacity
mv -T -- "$SB_CMD_INPUT"{.tmp,}
{
qrexec-client-vm -T "$SB_DISP" qubes.VMShell+WaitForSession \
2>&1 >&"$req_fd" {req_fd}>&- <&"$cmd_fd" |
sanitize_ascii {req_fd}>&- |
sed -u 's/^/disp: /' >&2 {req_fd}>&-
} {req_fd}>&1 |
sanitize_"$SB_CHARSET" |
while IFS= read -r req_line; do
readarray -d $'\t' -t req <<<"$req_line"
req[-1]=${req[-1]%$'\n'}
[[ $SB_CHARSET == ascii ]] || req[0]=$(sanitize_ascii <<<"${req[0]}")
req=( "${req[@]::2}" "${req[@]:${first_page_field[$SB_CHARSET]}:2}" )
case "${req[0]}" in
bookmark|login)
# shellcheck disable=SC2145
split-browser-"${req[@]}" &
;;
restart)
echo x >&"$saved_stdout_fd"
;;
*)
printf 'Unknown request from DisposableVM: %s\n' "${req[0]}"
;;
esac
done
)
# Main loop: Configure and open the disposable browser. After clean shutdown,
# do it again (if restart was requested).
while :; do
config_prefs=()
for f in {,/usr/local}/etc/split-browser/prefs/*.js; do
readarray -t -O ${#config_prefs[@]} config_prefs <"$f"
done
setup_cmd=( setup "${config_prefs[@]}" "${cli_prefs[@]}" )
master_cmd=( master "${args[@]}" )
args=() # on restart, don't load given URLs again
restart=$(dispvm "${setup_cmd[*]}" "${master_cmd[*]}")
[[ $restart ]] || exit 0
done