-
Notifications
You must be signed in to change notification settings - Fork 15
/
parcimonie.sh
executable file
·227 lines (204 loc) · 7.63 KB
/
parcimonie.sh
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#!/usr/bin/env bash
# Copyright © 2015 Etienne Perot <etienne at perot dot me>
# This work is free. You can redistribute it and/or modify it under the
# terms of the Do What The Fuck You Want To Public License, Version 2,
# as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
if [ -n "$PARCIMONIE_CONF" ]; then
source "$PARCIMONIE_CONF" || exit 'Bad configuration file.'
export PARCIMONIE_CONF='' # Children spawned by this script (if any) should not inherit those values
fi
parcimonieUser="${PARCIMONIE_USER:-$(whoami)}"
gnupgBinary="${GNUPG_BINARY:-}"
torsocksBinary="${TORSOCKS_BINARY:-torsocks}"
gnupgHomedir="${GNUPG_HOMEDIR:-}"
gnupgKeyserver="${GNUPG_KEYSERVER:-}"
gnupgKeyserverOptions="${GNUPG_KEYSERVER_OPTIONS:-http-proxy=}"
torAddress="${TOR_ADDRESS:-127.0.0.1}"
torPort="${TOR_PORT:-9050}"
minWaitTime="${MIN_WAIT_TIME:-900}" # 15 minutes
targetRefreshTime="${TARGET_REFRESH_TIME:-604800}" # 1 week
computerOnlineFraction="${COMPUTER_ONLINE_FRACTION:-1.0}" # 100% of the time
useRandom="${USE_RANDOM:-false}"
dirmngrPath="${DIRMNGR_PATH-}"
dirmngrClientPath="${DIRMNGR_CLIENT_PATH-}"
# -----------------------------------------------------------------------------
if [ "$(whoami)" != "$parcimonieUser" ]; then
if [ "$parcimonieUser" == '*' ]; then # If user requested the script to run for all users
if [ "$(id -u)" != 0 ]; then
echo 'Error: Must be run as root in order to support PARCIMONIE_USER="*".'
exit 1
fi
gnupgUsers=()
for user in $(getent passwd | cut -d ':' -f 1); do
if [ -d "$(eval "echo ~$user")/.gnupg" ]; then
gnupgUsers+=("$user")
fi
done
# If we have 0 users, error out
if [ "${#gnupgUsers[@]}" -eq 0 ]; then
echo 'Error: No users found with a ~/.gnupg directory.'
exit 1
fi
# If we just have one user, just su to it
if [ "${#gnupgUsers[@]}" -eq 1 ]; then
export PARCIMONIE_USER="${gnupgUsers[0]}"
export GNUPG_HOMEDIR="$(eval "echo ~"${gnupgUsers[0]}"")/.gnupg"
exec su -c "$0" "${gnupgUsers[0]}"
fi
# If we have more than one, spawn children processes for each
childrenPids=()
for user in "${gnupgUsers[@]}"; do
PARCIMONIE_USER="$user" GNUPG_HOMEDIR="$(eval "echo ~$user")/.gnupg" su -c "$0" "$user" &
childrenPids+=("$!")
done
for childPid in "${childrenPids[@]}"; do
wait "$childPid"
done
exit 0
else # If the user requested the script to run for a specific user which is not the current one
exec su -c "$0" "$parcimonieUser"
fi
fi
# If we get here, we know that we are the right user.
# Find the gpg binary.
if [ -n "$gnupgBinary" ]; then
if [ ! -x "$gnupgBinary" ]; then
echo "Error: GNUPG_BINARY '$GNUPG_BINARY' does not exist or is not executable."
exit 1
fi
elif which gpg2 &> /dev/null; then
# Try to find it in $PATH.
gnupgBinary="$(which gpg2)"
echo "Detected gpg2 at '$gnupgBinary'."
elif which gpg &> /dev/null; then
gnupgBinary="$(which gpg)"
echo "Detected gpg at '$gnupgBinary'."
else
echo 'gpg not found. Please make sure you have installed GnuPG.'
echo 'You may manually specify the full path to gpg with GNUPG_BINARY.'
exit 1
fi
# Test for dirmngr, used in GnuPG >= 2.1 for keyserver communication.
if [ -n "$dirmngrPath" ]; then
if [ ! -x "$dirmngrPath" ]; then
echo "Error: DIRMNGR_PATH '$DIRMNGR_PATH' does not exist or is not executable."
exit 1
fi
elif which dirmngr &> /dev/null; then
# Try to find dirmngr in $PATH.
dirmngrPath="$(which dirmngr)"
echo "Detected dirmngr at '$dirmngrPath'; assuming GnuPG >= 2.1."
else
echo 'dirmngr not specified, and not found in $PATH. Assuming GnuPG < 2.1.'
fi
if [ -n "$dirmngrPath" ]; then
# If we are using dirmngr, we must also have dirmngr-client.
if [ -n "$dirmngrClientPath" ]; then
if [ ! -x "$dirmngrClientPath" ]; then
echo "Error: DIRMNGR_CLIENT_PATH '$DIRMNGR_CLIENT_PATH' does not exist or is not executable."
exit 1
fi
elif which dirmngr-client &> /dev/null; then
# Try to find it in $PATH. Unlike dirmngr, it is a fatal error if we cannot find it,
# because we need it to handle dirmngr properly.
dirmngrClientPath="$(which dirmngr-client)"
echo "Detected dirmngr-client at '$dirmngrClientPath'."
else
echo "dirmngr-client not found, while dirmngr was found at '$dirmngrPath'."
echo 'Please make sure your installation of GnuPG is complete.'
echo 'You may manually specify the full path to dirmngr-client with DIRMNGR_CLIENT_PATH.'
exit 1
fi
fi
gnupgExec=("$gnupgBinary" --batch --with-colons)
if [ -n "$gnupgHomedir" ]; then
gnupgExec+=(--homedir "$gnupgHomedir")
fi
if [ -n "$gnupgKeyserver" ]; then
gnupgExec+=(--keyserver "$gnupgKeyserver")
fi
if [ -n "$gnupgKeyserverOptions" ]; then
gnupgExec+=(--keyserver-options "$gnupgKeyserverOptions")
fi
# Test for GNU `sed`, or use a `sed` fallback in sedExtRegexp
sedExec=(sed)
if [ "$(echo 'abc' | sed -r 's/abc/def/' 2> /dev/null || true)" == 'def' ]; then
# GNU Linux sed
sedExec+=(-r)
else
# Mac OS X sed
sedExec+=(-E)
fi
sedExtRegexp() {
"${sedExec[@]}" "$@"
}
keepDigitsOnly() {
sedExtRegexp -e 's/[^[:digit:]]//g' -e '/^$/d'
}
getRandom() {
if [ -z "$useRandom" -o "$useRandom" == 'false' ]; then
od -vAn -N4 -tu4 < /dev/urandom | keepDigitsOnly
else
od -vAn -N4 -tu4 < /dev/random | keepDigitsOnly
fi
}
nontor_gnupg() {
"${gnupgExec[@]}" "$@"
return "$?"
}
tor_gnupg() {
"$torsocksBinary" --isolate "${gnupgExec[@]}" "$@"
}
getPublicKeys() {
nontor_gnupg --list-public-keys --with-colons --fixed-list-mode --with-fingerprint --with-fingerprint --with-key-data |
grep -a -A 1 '^pub:' | # only allow fingerprints of public keys (not subkeys)
grep -E '^fpr:+[0-9a-fA-F]{40,}:' | # only allow fingerprints of v4 pgp keys
# (v3 fingerprints consist of 32 hex characters)
sedExtRegexp 's/^fpr:+([0-9a-fA-F]+):+$/\1/' # extract the fingerprint
}
getNumKeys() {
getPublicKeys | wc -l | keepDigitsOnly
}
getRandomKey() {
local allPublicKeys fingerprint
allPublicKeys=()
for fingerprint in $(getPublicKeys); do
allPublicKeys+=("$fingerprint")
done
echo "${allPublicKeys[$(expr "$(getRandom)" % "${#allPublicKeys[@]}")]}"
}
getTimeToWait() {
# The target refresh time is scaled by the fraction of time that the computer is expected to be online.
# expr or bash's $(()) don't support fractional math. Use awk.
local scaledRefreshTime
scaledRefreshTime="$targetRefreshTime"
if [ "$computerOnlineFraction" != '1.0' -a "$computerOnlineFraction" != '1' ]; then
scaledRefreshTime="$(echo "$scaledRefreshTime" "$computerOnlineFraction" | awk 'BEGIN {print sprintf("%.0f", $1 * $2)}')"
fi
# minimum wait time + rand(2 * (refresh time / number of pubkeys))
# = $minWaitTime + $(getRandom) % (2 * $scaledRefreshTime / $(getNumKeys))
# But if we have a lot of keys or a very short refresh time (2 * refresh time < number of keys),
# then we can encounter a modulo by zero. In this case, we use the following as fallback:
# minimum wait time + rand(minimum wait time)
# = $minWaitTime + $(getRandom) % $minWaitTime
if [ "$(expr '2' '*' "$scaledRefreshTime")" -le "$(getNumKeys)" ]; then
expr "$minWaitTime" '+' "$(getRandom)" '%' "$minWaitTime"
else
expr "$minWaitTime" '+' "$(getRandom)" '%' '(' '2' '*' "$scaledRefreshTime" '/' "$(getNumKeys)" ')'
fi
}
if [ "$(getNumKeys)" -eq 0 ]; then
echo 'No GnuPG keys found.'
exit 1
fi
if [ "$(echo "$computerOnlineFraction" | awk '{ print ($1 < 0.1 || $1 > 1.0) ? "bad" : "good" }')" == 'bad' ]; then
echo 'COMPUTER_ONLINE_FRACTION must be between 0.1 and 1.0.' >&2
exit 1
fi
while true; do
keyToRefresh="$(getRandomKey)"
timeToSleep="$(getTimeToWait)"
echo "> Sleeping $timeToSleep seconds before refreshing key $keyToRefresh..."
sleep "$timeToSleep"
tor_gnupg --recv-keys "$keyToRefresh"
done