git » chasquid » commit 948cee1

Improve bash quoting, and other similar best practices

author Alberto Bertogli
2022-11-13 00:37:28 UTC
committer Alberto Bertogli
2022-11-13 11:09:19 UTC
parent e2481b07a9ca8cb548dd6b28cc3b2cb0fc0fb874

Improve bash quoting, and other similar best practices

This patch updates the shell scripts with some of the common best
practices, which should make them more resilient to unusual failures and
unexpected environments (in particular, directories with spaces).

Most of these were identified by shellcheck.

cmd/chasquid-util/test.sh +1 -1
cmd/dovecot-auth-cli/test.sh +4 -3
cmd/mda-lmtp/test.sh +3 -3
docker/add-user.sh +3 -3
docker/entrypoint.sh +9 -6
docs/man/generate.sh +5 -5
test/cover.sh +4 -4
test/run.sh +4 -4
test/stress-01-load/run.sh +6 -6
test/stress-02-connections/run.sh +5 -5
test/stress.sh +4 -4
test/t-01-simple_local/run.sh +1 -1
test/t-02-exim/get-exim4-debian.sh +3 -3
test/t-02-exim/run.sh +1 -1
test/t-03-queue_persistency/run.sh +1 -1
test/t-04-aliases/run.sh +6 -6
test/t-05-null_address/run.sh +1 -1
test/t-06-idna/run.sh +1 -1
test/t-07-smtputf8/run.sh +1 -1
test/t-09-loop/run.sh +3 -3
test/t-10-hooks/run.sh +2 -2
test/t-11-dovecot/run.sh +3 -3
test/t-12-minor_dialogs/run.sh +3 -3
test/t-13-reload/run.sh +2 -2
test/t-14-tls_tracking/run.sh +1 -1
test/t-15-driusan_dkim/run.sh +1 -1
test/t-16-spf/run.sh +2 -2
test/t-17-maillog/run.sh +1 -1
test/t-18-haproxy/run.sh +1 -1
test/t-19-dkimpy/run.sh +1 -1
test/util/docker_entrypoint.sh +3 -3
test/util/lib.sh +47 -44

diff --git a/cmd/chasquid-util/test.sh b/cmd/chasquid-util/test.sh
index 8b884d7..d3c13e3 100755
--- a/cmd/chasquid-util/test.sh
+++ b/cmd/chasquid-util/test.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 set -e
-. $(dirname ${0})/../../test/util/lib.sh
+. "$(dirname "$0")/../../test/util/lib.sh"
 
 init
 
diff --git a/cmd/dovecot-auth-cli/test.sh b/cmd/dovecot-auth-cli/test.sh
index c7dc23c..f837946 100755
--- a/cmd/dovecot-auth-cli/test.sh
+++ b/cmd/dovecot-auth-cli/test.sh
@@ -1,13 +1,14 @@
 #!/bin/bash
 
 set -e
-. $(dirname ${0})/../../test/util/lib.sh
+. "$(dirname "$0")/../../test/util/lib.sh"
 
 init
 
 # Build the binary once, so we can use it and launch it in chamuyero scripts.
 # Otherwise, we not only spend time rebuilding it over and over, but also "go
 # run" masks the exit code, which is something we care about.
+# shellcheck disable=SC2086
 if [ "${COVER_DIR}" != "" ]; then
 	go test -covermode=count -coverpkg=../../... -c \
 		$GOFLAGS -tags="coveragebin $GOTAGS"
@@ -22,9 +23,9 @@ if ! ./dovecot-auth-cli lalala 2>&1 | grep -q "invalid arguments"; then
 fi
 
 for i in *.cmy; do
-	if ! chamuyero $i > $i.log 2>&1 ; then
+	if ! chamuyero "$i" > "$i.log" 2>&1 ; then
 		echo "# Test $i failed, log follows"
-		cat $i.log
+		cat "$i.log"
 		exit 1
 	fi
 done
diff --git a/cmd/mda-lmtp/test.sh b/cmd/mda-lmtp/test.sh
index 26f4364..b9c5b73 100755
--- a/cmd/mda-lmtp/test.sh
+++ b/cmd/mda-lmtp/test.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 set -e
-. $(dirname ${0})/../../test/util/lib.sh
+. "$(dirname "$0")/../../test/util/lib.sh"
 
 init
 
@@ -11,9 +11,9 @@ init
 go build
 
 for i in *.cmy; do
-	if ! chamuyero $i > $i.log 2>&1 ; then
+	if ! chamuyero "$i" > "$i.log" 2>&1 ; then
 		echo "# Test $i failed, log follows"
-		cat $i.log
+		cat "$i.log"
 		exit 1
 	fi
 done
diff --git a/docker/add-user.sh b/docker/add-user.sh
index ae49e7d..8bf78be 100755
--- a/docker/add-user.sh
+++ b/docker/add-user.sh
@@ -7,7 +7,7 @@
 
 set -e
 
-read -p "Email (full user@domain format): " EMAIL
+read -r -p "Email (full user@domain format): " EMAIL
 
 if ! echo "${EMAIL}" | grep -q @; then
 	echo "Error: email should have '@'."
@@ -15,7 +15,7 @@ if ! echo "${EMAIL}" | grep -q @; then
 fi
 
 
-read -p "Password: " -s PASSWORD
+read -r -p "Password: " -s PASSWORD
 echo
 
 DOMAIN=$(echo echo "${EMAIL}" | cut -d '@' -f 2)
@@ -33,7 +33,7 @@ mkdir -p /data/dovecot
 touch /data/dovecot/users
 if grep -q "^${EMAIL}:" /data/dovecot/users; then
 	cp /data/dovecot/users /data/dovecot/users.old
-	cat /data/dovecot/users.old | grep -v "^${EMAIL}:" \
+	grep -v "^${EMAIL}:" /data/dovecot/users.old \
 		> /data/dovecot/users
 fi
 
diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh
index e5a5451..7093000 100755
--- a/docker/entrypoint.sh
+++ b/docker/entrypoint.sh
@@ -28,15 +28,16 @@ if [ "$AUTO_CERTS" != "" ]; then
 		MAIL_OPTS="-m $CERTS_MAIL"
 	fi
 
-	for DOMAIN in $(echo $AUTO_CERTS); do
+	for DOMAIN in $AUTO_CERTS; do
 		# If it has never been set up, then do so.
-		if ! [ -e /etc/letsencrypt/live/$DOMAIN/fullchain.pem ]; then
+		if ! [ -e "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" ]; then
+			# shellcheck disable=SC2086
 			certbot certonly \
 				--non-interactive \
 				--standalone \
 				--agree-tos \
 				$MAIL_OPTS \
-				-d $DOMAIN
+				-d "$DOMAIN"
 		else
 			echo "$DOMAIN certificate already set up."
 		fi
@@ -53,9 +54,10 @@ if [ "$AUTO_CERTS" != "" ]; then
 fi
 
 CERT_DOMAINS=""
-for i in $(ls /etc/letsencrypt/live/); do
-	if [ -e "/etc/letsencrypt/live/$i/fullchain.pem" ]; then
-		CERT_DOMAINS="$CERT_DOMAINS $i"
+for i in /etc/letsencrypt/live/*; do
+	D="${i##*/}"  # Extract the last component of the path.
+	if [ -e "/etc/letsencrypt/live/$D/fullchain.pem" ]; then
+		CERT_DOMAINS="$CERT_DOMAINS $D"
 	fi
 done
 
@@ -104,4 +106,5 @@ echo "hostname: '$ONE_DOMAIN'" >> /etc/chasquid/chasquid.conf
 start-stop-daemon --start --quiet --pidfile /run/dovecot.pid \
 	--exec /usr/sbin/dovecot -- -c /etc/dovecot/dovecot.conf
 
+# shellcheck disable=SC2086
 sudo -u chasquid -g chasquid  /usr/bin/chasquid  $CHASQUID_FLAGS
diff --git a/docs/man/generate.sh b/docs/man/generate.sh
index 2f29a58..c145e90 100755
--- a/docs/man/generate.sh
+++ b/docs/man/generate.sh
@@ -8,20 +8,20 @@
 set -e
 
 for IN in *.pod; do
-	OUT=$( basename $IN .pod )
+	OUT=$(basename "$IN" .pod)
 	SECTION=${OUT##*.}
 	NAME=${OUT%.*}
 
 	# If it has not changed in git, set the mtime to the last commit that
 	# touched the file.
 	CHANGED=$( git status --porcelain -- "$IN" | wc -l )
-	if [ $CHANGED -eq 0 ]; then
+	if [ "$CHANGED" -eq 0 ]; then
 		GIT_MTIME=$( git log --pretty=%at -n1 -- "$IN" )
 		touch -d "@$GIT_MTIME" "$IN"
 	fi
 
-	podchecker $IN
-	pod2man --section=$SECTION --name=$NAME \
+	podchecker "$IN"
+	pod2man --section="$SECTION" --name="$NAME" \
 		--release "" --center "" \
-		$IN $OUT
+		"$IN" "$OUT"
 done
diff --git a/test/cover.sh b/test/cover.sh
index 9479f49..adccaaa 100755
--- a/test/cover.sh
+++ b/test/cover.sh
@@ -10,7 +10,7 @@
 # coverage_test.go), but works for now.
 
 set -e
-. $(dirname ${0})/util/lib.sh
+. "$(dirname "$0")/util/lib.sh"
 
 init
 
@@ -28,11 +28,11 @@ export COVER_DIR="$PWD/.coverage"
 # tests, which don't expect any expvars to exists besides the one registered
 # in the tests themselves.
 for pkg in $(go list ./... | grep -v -E 'chasquid/cmd/|chasquid/test'); do
-	OUT_FILE="$COVER_DIR/pkg-`echo $pkg | sed s+/+_+g`.out"
+	OUT_FILE="$COVER_DIR/pkg-${pkg//\//_}.out"
 	go test -tags coverage \
 		-covermode=count \
 		-coverprofile="$OUT_FILE" \
-		-coverpkg=./... $pkg
+		-coverpkg=./... "$pkg"
 done
 
 # Integration tests.
@@ -61,5 +61,5 @@ go run "${UTILDIR}/coverhtml/coverhtml.go" \
 echo
 echo
 echo "Coverage report can be found in:"
-echo file://$COVER_DIR/coverage.html
+echo "file://$COVER_DIR/coverage.html"
 
diff --git a/test/run.sh b/test/run.sh
index c23eab9..6d829c0 100755
--- a/test/run.sh
+++ b/test/run.sh
@@ -1,16 +1,16 @@
 #!/bin/bash
 
 set -e
-. $(dirname ${0})/util/lib.sh
+. "$(dirname "$0")/util/lib.sh"
 
 init
 
 FAILED=0
 
 for i in t-*; do
-	echo $i ...
-	setsid -w $i/run.sh
-	FAILED=$(( $FAILED + $? ))
+	echo "$i" ...
+	setsid -w "$i/run.sh"
+	FAILED=$(( FAILED + $? ))
 	echo
 done
 
diff --git a/test/stress-01-load/run.sh b/test/stress-01-load/run.sh
index 1b2600e..0441f53 100755
--- a/test/stress-01-load/run.sh
+++ b/test/stress-01-load/run.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 
@@ -14,18 +14,18 @@ mkdir -p .logs
 chasquid -v=-1 --logfile=.logs/chasquid.log --config_dir=config &
 wait_until_ready 1025
 
-echo Peak RAM: `chasquid_ram_peak`
+echo "Peak RAM: $(chasquid_ram_peak)"
 
 if ! loadgen -logtime -addr=localhost:1025 -run_for=3s -noop; then
-	fail
+	fail "loadgen -noop error"
 fi
 
-echo Peak RAM: `chasquid_ram_peak`
+echo "Peak RAM: $(chasquid_ram_peak)"
 
 if ! loadgen -logtime -addr=localhost:1025 -run_for=3s; then
-	fail
+	fail "loadgen error"
 fi
 
-echo Peak RAM: `chasquid_ram_peak`
+echo "Peak RAM: $(chasquid_ram_peak)"
 
 success
diff --git a/test/stress-02-connections/run.sh b/test/stress-02-connections/run.sh
index 1fb4bbd..a237988 100755
--- a/test/stress-02-connections/run.sh
+++ b/test/stress-02-connections/run.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 
@@ -14,22 +14,22 @@ mkdir -p .logs
 chasquid -v=-1 --logfile=.logs/chasquid.log --config_dir=config &
 wait_until_ready 1025
 
-echo Peak RAM: `chasquid_ram_peak`
+echo "Peak RAM: $(chasquid_ram_peak)"
 
 # Set connection count to (max open files) - (leeway).
 # We set the leeway to account for file descriptors opened by the runtime and
 # listeners; 20 should be enough for now.
 # Cap it to 2000, as otherwise it can be problematic due to port availability.
-COUNT=$(( `ulimit -n` - 20 ))
+COUNT=$(( $(ulimit -n) - 20 ))
 if [ $COUNT -gt 2000 ]; then
 	COUNT=2000
 fi
 
 if ! conngen -logtime -addr=localhost:1025 -count=$COUNT; then
 	tail -n 1 .logs/chasquid.log
-	fail
+	fail "conngen error"
 fi
 
-echo Peak RAM: `chasquid_ram_peak`
+echo "Peak RAM: $(chasquid_ram_peak)"
 
 success
diff --git a/test/stress.sh b/test/stress.sh
index 1a8016f..7409d3f 100755
--- a/test/stress.sh
+++ b/test/stress.sh
@@ -1,16 +1,16 @@
 #!/bin/bash
 
 set -e
-. $(dirname ${0})/util/lib.sh
+. "$(dirname "$0")/util/lib.sh"
 
 init
 
 FAILED=0
 
 for i in stress-*; do
-	echo $i ...
-	setsid -w $i/run.sh
-	FAILED=$(( $FAILED + $? ))
+	echo "$i ..."
+	setsid -w "$i/run.sh"
+	FAILED=$(( FAILED + $? ))
 	echo
 done
 
diff --git a/test/t-01-simple_local/run.sh b/test/t-01-simple_local/run.sh
index b353869..8427f11 100755
--- a/test/t-01-simple_local/run.sh
+++ b/test/t-01-simple_local/run.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 check_hostaliases
diff --git a/test/t-02-exim/get-exim4-debian.sh b/test/t-02-exim/get-exim4-debian.sh
index c5cbcb2..076e02a 100755
--- a/test/t-02-exim/get-exim4-debian.sh
+++ b/test/t-02-exim/get-exim4-debian.sh
@@ -5,19 +5,19 @@
 # given the nature of these tests that's acceptable for now.
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 
 # Download and extract the package in .exim-bin
 apt download exim4-daemon-light
-dpkg -x exim4-daemon-light_*.deb $PWD/.exim-bin/
+dpkg -x exim4-daemon-light_*.deb "$PWD/.exim-bin/"
 
 # Create a symlink to .exim4, which is the directory we will use to store
 # configuration, spool, etc.
 # The configuration template will look for it here.
 mkdir -p .exim4
-ln -sf $PWD/.exim-bin/usr/sbin/exim4 .exim4/
+ln -sf "$PWD/.exim-bin/usr/sbin/exim4" .exim4/
 
 # Remove the setuid bit, if there is one - we don't need it and may cause
 # confusion and/or security troubles.
diff --git a/test/t-02-exim/run.sh b/test/t-02-exim/run.sh
index 69b6677..28dbadc 100755
--- a/test/t-02-exim/run.sh
+++ b/test/t-02-exim/run.sh
@@ -23,7 +23,7 @@
 #   chasquid will receive the email from exim, and deliver it locally.
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 check_hostaliases
diff --git a/test/t-03-queue_persistency/run.sh b/test/t-03-queue_persistency/run.sh
index 2ccf8fb..c8d52e0 100755
--- a/test/t-03-queue_persistency/run.sh
+++ b/test/t-03-queue_persistency/run.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 
diff --git a/test/t-04-aliases/run.sh b/test/t-04-aliases/run.sh
index c500c31..dcfe5ba 100755
--- a/test/t-04-aliases/run.sh
+++ b/test/t-04-aliases/run.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 check_hostaliases
@@ -14,12 +14,12 @@ chasquid -v=2 --logfile=.logs/chasquid.log --config_dir=config &
 wait_until_ready 1025
 
 function send_and_check() {
-	run_msmtp $1@testserver < content
+	run_msmtp "$1@testserver" < content
 	shift
-	for i in $@; do
-		wait_for_file .mail/$i@testserver
-		mail_diff content .mail/$i@testserver
-		rm -f .mail/$i@testserver
+	for i in "$@"; do
+		wait_for_file ".mail/$i@testserver"
+		mail_diff content ".mail/$i@testserver"
+		rm -f ".mail/$i@testserver"
 	done
 }
 
diff --git a/test/t-05-null_address/run.sh b/test/t-05-null_address/run.sh
index 92a3abc..3638ac1 100755
--- a/test/t-05-null_address/run.sh
+++ b/test/t-05-null_address/run.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 check_hostaliases
diff --git a/test/t-06-idna/run.sh b/test/t-06-idna/run.sh
index 3e6c85b..bc6c1bd 100755
--- a/test/t-06-idna/run.sh
+++ b/test/t-06-idna/run.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 check_hostaliases
diff --git a/test/t-07-smtputf8/run.sh b/test/t-07-smtputf8/run.sh
index e0be8ba..5ca6d5c 100755
--- a/test/t-07-smtputf8/run.sh
+++ b/test/t-07-smtputf8/run.sh
@@ -5,7 +5,7 @@
 # capitalizations.
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 
diff --git a/test/t-09-loop/run.sh b/test/t-09-loop/run.sh
index fe2e0b4..21561b9 100755
--- a/test/t-09-loop/run.sh
+++ b/test/t-09-loop/run.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 check_hostaliases
@@ -45,8 +45,8 @@ run_msmtp aliasB@srv-B < content
 # Get some of the debugging pages, for troubleshooting, and to make sure they
 # work reasonably well.
 function fexp_gt10() {
-	fexp $1 -save $2 && \
-		[ $( cat $2 | wc -l ) -gt 10 ]
+	fexp "$1" -save "$2" && \
+		[ "$( wc -l < "$2" )" -gt 10 ]
 }
 
 fexp_gt10 http://localhost:1099/ .data-A/dbg-root \
diff --git a/test/t-10-hooks/run.sh b/test/t-10-hooks/run.sh
index 918fed4..a29aebc 100755
--- a/test/t-10-hooks/run.sh
+++ b/test/t-10-hooks/run.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 check_hostaliases
@@ -70,7 +70,7 @@ fi
 
 # Check that the bad hooks don't prevent delivery.
 for i in config/hooks/post-data.bad*; do
-	cp $i config/hooks/post-data
+	cp "$i" config/hooks/post-data
 
 	run_msmtp someone@testserver < content
 	wait_for_file .mail/someone@testserver
diff --git a/test/t-11-dovecot/run.sh b/test/t-11-dovecot/run.sh
index 2a96b32..8f3d8d7 100755
--- a/test/t-11-dovecot/run.sh
+++ b/test/t-11-dovecot/run.sh
@@ -7,7 +7,7 @@
 #  - dovecot listening on unix sockets in .dovecot/
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 check_hostaliases
@@ -26,8 +26,8 @@ export ROOT="/tmp/chasquid-dovecot-test"
 mkdir -p $ROOT $ROOT/run $ROOT/lib
 rm -f $ROOT/dovecot.log
 
-export GROUP=$(id -g -n)
-envsubst < config/dovecot.conf.in > $ROOT/dovecot.conf
+GROUP=$(id -g -n) envsubst \
+	< config/dovecot.conf.in > $ROOT/dovecot.conf
 cp -f config/passwd $ROOT/passwd
 
 dovecot -F -c $ROOT/dovecot.conf &
diff --git a/test/t-12-minor_dialogs/run.sh b/test/t-12-minor_dialogs/run.sh
index 3c3f570..e15277b 100755
--- a/test/t-12-minor_dialogs/run.sh
+++ b/test/t-12-minor_dialogs/run.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 
@@ -14,13 +14,13 @@ wait_until_ready 1025
 
 FAILED=0
 for i in *.cmy; do
-	if ! chamuyero $i > .logs/$i.log 2>&1 ; then
+	if ! chamuyero "$i" > ".logs/$i.log" 2>&1 ; then
 		echo "test $i failed, see .logs/$i.log"
 		FAILED=1
 	fi
 done
 
 if [ $FAILED == 1 ]; then
-	fail
+	fail "got at least one error"
 fi
 success
diff --git a/test/t-13-reload/run.sh b/test/t-13-reload/run.sh
index 2dcfee0..00db0ed 100755
--- a/test/t-13-reload/run.sh
+++ b/test/t-13-reload/run.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 check_hostaliases
@@ -72,6 +72,6 @@ fexp http://localhost:1099/exit -status 405
 # eventually exit.
 CHASQUID_PID=$(pgrep -s 0 chasquid)
 fexp http://localhost:1099/exit -method POST -bodyre "OK"
-wait_until ! kill -s 0 $CHASQUID_PID 2> /dev/null
+wait_until ! kill -s 0 "$CHASQUID_PID" 2> /dev/null
 
 success
diff --git a/test/t-14-tls_tracking/run.sh b/test/t-14-tls_tracking/run.sh
index 17e696f..1ecbe30 100755
--- a/test/t-14-tls_tracking/run.sh
+++ b/test/t-14-tls_tracking/run.sh
@@ -3,7 +3,7 @@
 # Test TLS tracking features, which require faking SPF.
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 check_hostaliases
diff --git a/test/t-15-driusan_dkim/run.sh b/test/t-15-driusan_dkim/run.sh
index 2bcb3c7..45958c1 100755
--- a/test/t-15-driusan_dkim/run.sh
+++ b/test/t-15-driusan_dkim/run.sh
@@ -4,7 +4,7 @@
 # https://github.com/driusan/dkim
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 check_hostaliases
diff --git a/test/t-16-spf/run.sh b/test/t-16-spf/run.sh
index 36c37a6..3eafbe0 100755
--- a/test/t-16-spf/run.sh
+++ b/test/t-16-spf/run.sh
@@ -5,7 +5,7 @@
 # main gaps.
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 check_hostaliases
@@ -47,7 +47,7 @@ function launch_minidns() {
 		kill $MINIDNS
 		wait $MINIDNS || true
 	fi
-	cp $1 .zones
+	cp "$1" .zones
 	minidns_bg --addr=":9053" -zones=.zones >> .minidns.log 2>&1
 	wait_until_ready 9053
 }
diff --git a/test/t-17-maillog/run.sh b/test/t-17-maillog/run.sh
index e3eb360..43dd1be 100755
--- a/test/t-17-maillog/run.sh
+++ b/test/t-17-maillog/run.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 check_hostaliases
diff --git a/test/t-18-haproxy/run.sh b/test/t-18-haproxy/run.sh
index 98d0185..88e3884 100755
--- a/test/t-18-haproxy/run.sh
+++ b/test/t-18-haproxy/run.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 check_hostaliases
diff --git a/test/t-19-dkimpy/run.sh b/test/t-19-dkimpy/run.sh
index 48e06f5..e79ffa5 100755
--- a/test/t-19-dkimpy/run.sh
+++ b/test/t-19-dkimpy/run.sh
@@ -3,7 +3,7 @@
 # Test integration with dkimpy.
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 check_hostaliases
diff --git a/test/util/docker_entrypoint.sh b/test/util/docker_entrypoint.sh
index 451aa77..bf8360c 100755
--- a/test/util/docker_entrypoint.sh
+++ b/test/util/docker_entrypoint.sh
@@ -8,7 +8,7 @@
 # This is used for more hermetic Docker test environments.
 
 set -e
-. $(dirname ${0})/../util/lib.sh
+. "$(dirname "$0")/../util/lib.sh"
 
 init
 
@@ -45,7 +45,7 @@ export GOPROXY=off
 # Launch arguments, which come from docker CMD, as "chasquid" user.
 # Running tests as root makes some integration tests more difficult, as for
 # example Exim has hard-coded protections against running as root.
-sudo -u chasquid -g chasquid \
+sudo -u "chasquid" -g "chasquid" \
 	--set-home \
-	--preserve-env PATH=${PATH} \
+	--preserve-env PATH="$PATH" \
 	-- "$@"
diff --git a/test/util/lib.sh b/test/util/lib.sh
index 87b68b8..9b6aa2b 100644
--- a/test/util/lib.sh
+++ b/test/util/lib.sh
@@ -1,3 +1,4 @@
+#!/bin/bash
 # Library to write the shell scripts in the tests.
 
 function init() {
@@ -5,10 +6,11 @@ function init() {
 		set -v
 	fi
 
-	export UTILDIR="$( realpath `dirname "${BASH_SOURCE[0]}"` )"
+	UTILDIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")" )
+	export UTILDIR
 
-	export TBASE="$(realpath `dirname ${0}`)"
-	cd ${TBASE}
+	TBASE=$(realpath "$(dirname "$0")" )
+	cd "${TBASE}" || exit 1
 
 	if [ "${RACE}" == "1" ]; then
 		GOFLAGS="$GOFLAGS -race"
@@ -30,7 +32,8 @@ function chasquid() {
 		return
 	fi
 
-	( cd ${TBASE}/../../; go build $GOFLAGS -tags="$GOTAGS" . )
+	# shellcheck disable=SC2086
+	( cd "${TBASE}/../../" || exit 1; go build $GOFLAGS -tags="$GOTAGS" . )
 
 	# HOSTALIASES: so we "fake" hostnames.
 	# PATH: so chasquid can call test-mda without path issues.
@@ -38,13 +41,14 @@ function chasquid() {
 	HOSTALIASES=${TBASE}/hosts \
 	PATH=${UTILDIR}:${PATH} \
 	MDA_DIR=${TBASE}/.mail \
-		${TBASE}/../../chasquid "$@"
+		"${TBASE}/../../chasquid" "$@"
 }
 
 function chasquid_cover() {
 	# Build the coverage-enabled binary.
 	# See coverage_test.go for more details.
-	( cd ${TBASE}/../../;
+	# shellcheck disable=SC2086
+	( cd "${TBASE}/../../" || exit 1;
 	  go test -covermode=count -coverpkg=./... -c \
 		  -tags="coveragebin $GOTAGS" $GOFLAGS )
 
@@ -54,9 +58,9 @@ function chasquid_cover() {
 	HOSTALIASES=${TBASE}/hosts \
 	PATH=${UTILDIR}:${PATH} \
 	MDA_DIR=${TBASE}/.mail \
-		${TBASE}/../../chasquid.test \
+		"${TBASE}/../../chasquid.test" \
 			-test.run "^TestRunMain$" \
-			-test.coverprofile="$COVER_DIR/test-`date +%s.%N`.out" \
+			-test.coverprofile="$COVER_DIR/test-$(date +%s.%N).out" \
 			"$@"
 }
 
@@ -65,9 +69,9 @@ function chasquid_cover() {
 # use the simpler add_user (below) for testing purposes.
 function chasquid-util-user-add() {
 	CONFDIR="${CONFDIR:-config}"
-	DOMAIN=$(echo $1 | cut -d @ -f 2)
+	DOMAIN=$(echo "$1" | cut -d @ -f 2)
 	mkdir -p "${CONFDIR}/domains/$DOMAIN/"
-	go run ${TBASE}/../../cmd/chasquid-util/chasquid-util.go \
+	go run "${TBASE}/../../cmd/chasquid-util/chasquid-util.go" \
 		-C="${CONFDIR}" \
 		user-add "$1" \
 		--password="$2" \
@@ -76,8 +80,8 @@ function chasquid-util-user-add() {
 
 function add_user() {
 	CONFDIR="${CONFDIR:-config}"
-	USERNAME=$(echo $1 | cut -d @ -f 1)
-	DOMAIN=$(echo $1 | cut -d @ -f 2)
+	USERNAME=$(echo "$1" | cut -d @ -f 1)
+	DOMAIN=$(echo "$1" | cut -d @ -f 2)
 	USERDB="${CONFDIR}/domains/$DOMAIN/users"
 	mkdir -p "${CONFDIR}/domains/$DOMAIN/"
 	if ! [ -f "${USERDB}" ] || ! grep -E -q "key:.*${USERNAME}" "${USERDB}"; then
@@ -87,7 +91,7 @@ function add_user() {
 }
 
 function dovecot-auth-cli() {
-	go run ${TBASE}/../../cmd/dovecot-auth-cli/dovecot-auth-cli.go "$@"
+	go run "${TBASE}/../../cmd/dovecot-auth-cli/dovecot-auth-cli.go" "$@"
 }
 
 function run_msmtp() {
@@ -97,54 +101,54 @@ function run_msmtp() {
 	# msmtp binary is often g+s, which causes $HOSTALIASES to not be
 	# honoured, which breaks the tests. Copy the binary to remove the
 	# setgid bit as a workaround.
-	cp -u "`command -v msmtp`" "${UTILDIR}/.msmtp-bin"
+	cp -u "$(command -v msmtp)" "${UTILDIR}/.msmtp-bin"
 
 	HOSTALIASES=${TBASE}/hosts \
-		${UTILDIR}/.msmtp-bin -C msmtprc "$@"
+		"${UTILDIR}/.msmtp-bin" -C msmtprc "$@"
 }
 
 function smtpc.py() {
-	${UTILDIR}/smtpc.py "$@"
+	"${UTILDIR}/smtpc.py" "$@"
 }
 
 function mail_diff() {
-	${UTILDIR}/mail_diff "$@"
+	"${UTILDIR}/mail_diff" "$@"
 }
 
 function chamuyero() {
-	${UTILDIR}/chamuyero "$@"
+	"${UTILDIR}/chamuyero" "$@"
 }
 
 function generate_cert() {
-	( cd ${UTILDIR}/generate_cert/; go build )
-	${UTILDIR}/generate_cert/generate_cert "$@"
+	( cd "${UTILDIR}/generate_cert/" || exit 1; go build )
+	"${UTILDIR}/generate_cert/generate_cert" "$@"
 }
 
 function loadgen() {
-	( cd ${UTILDIR}/loadgen/; go build )
-	${UTILDIR}/loadgen/loadgen "$@"
+	( cd "${UTILDIR}/loadgen/" || exit 1; go build )
+	"${UTILDIR}/loadgen/loadgen" "$@"
 }
 
 function conngen() {
-	( cd ${UTILDIR}/conngen/; go build )
-	${UTILDIR}/conngen/conngen "$@"
+	( cd "${UTILDIR}/conngen/" || exit 1; go build )
+	"${UTILDIR}/conngen/conngen" "$@"
 }
 
 function minidns_bg() {
-	( cd ${UTILDIR}/minidns; go build )
-	${UTILDIR}/minidns/minidns "$@" &
-	MINIDNS=$!
+	( cd "${UTILDIR}/minidns" || exit 1; go build )
+	"${UTILDIR}/minidns/minidns" "$@" &
+	export MINIDNS=$!
 }
 
 function fexp() {
-	( cd ${UTILDIR}/fexp/; go build )
-	${UTILDIR}/fexp/fexp "$@"
+	( cd "${UTILDIR}/fexp/" || exit 1; go build )
+	"${UTILDIR}/fexp/fexp" "$@"
 }
 
 function timeout() {
 	MYPID=$$
 	(
-		sleep $1
+		sleep "$1"
 		echo "timed out after $1, killing test"
 		kill -9 $MYPID
 	) &
@@ -155,18 +159,18 @@ function success() {
 }
 
 function skip() {
-	echo skipped: $*
+	echo "skipped: $*"
 	exit 0
 }
 
 function fail() {
-	echo FAILED: $*
+	echo "FAILED: $*"
 	exit 1
 }
 
 function check_hostaliases() {
 	if ! "${UTILDIR}/check-hostaliases"; then
-		skip '$HOSTALIASES not working (probably systemd-resolved)'
+		skip "\$HOSTALIASES not working (probably systemd-resolved)"
 	fi
 }
 
@@ -181,14 +185,14 @@ function wait_until_ready() {
 
 # Wait for the given file to exist.
 function wait_for_file() {
-	while ! [ -e ${1} ]; do
+	while ! [ -e "$1" ]; do
 		sleep 0.01
 	done
 }
 
 function wait_until() {
 	while true; do
-		if eval "$@"; then
+		if eval "$*"; then
 			return 0
 		fi
 		sleep 0.01
@@ -204,16 +208,16 @@ function generate_certs_for() {
 	CACHEDIR="${TBASE}/../.generate_certs_cache"
 	mkdir -p "${CACHEDIR}"
 	touch -d "10 minutes ago" "${CACHEDIR}/.reference"
-	if [ "${CACHEDIR}/${1}/" -ot "${CACHEDIR}/.reference" ]; then
+	if [ "${CACHEDIR}/$1/" -ot "${CACHEDIR}/.reference" ]; then
 		# Cache miss (either was not there, or was too old).
-		mkdir -p "${CACHEDIR}/${1}/"
+		mkdir -p "${CACHEDIR}/$1/"
 		(
-			cd "${CACHEDIR}/${1}/"
-			generate_cert -ca -validfor=1h -host=${1}
+			cd "${CACHEDIR}/$1/" || exit 1
+			generate_cert -ca -validfor=1h -host="$1"
 		)
 	fi
-	mkdir -p "${CONFDIR}/certs/${1}/"
-	cp -p "${CACHEDIR}/${1}"/* "${CONFDIR}/certs/${1}/"
+	mkdir -p "${CONFDIR}/certs/$1/"
+	cp -p "${CACHEDIR}/$1"/* "${CONFDIR}/certs/$1/"
 }
 
 # Check the Python version, and skip if it's too old.
@@ -229,7 +233,6 @@ function skip_if_python_is_too_old() {
 function chasquid_ram_peak() {
 	# Find the pid of the daemon, which we expect is running on the
 	# background somewhere within our current session.
-	SERVER_PID=`pgrep -s 0 -x chasquid`
-
-	echo $( cat /proc/$SERVER_PID/status | grep VmHWM | cut -d ':' -f 2- )
+	SERVER_PID=$(pgrep -s 0 -x chasquid)
+	grep VmHWM "/proc/$SERVER_PID/status" | cut -d ':' -f 2-
 }