summaryrefslogtreecommitdiff
path: root/vidoas
diff options
context:
space:
mode:
Diffstat (limited to 'vidoas')
-rwxr-xr-xvidoas173
1 files changed, 173 insertions, 0 deletions
diff --git a/vidoas b/vidoas
new file mode 100755
index 0000000..bc01d63
--- /dev/null
+++ b/vidoas
@@ -0,0 +1,173 @@
+#!/bin/sh
+
+# Copyright (c) 2020 Kimmo Suominen <kim@netbsd.org>
+# Copyright (c) 2022 Thomas Voss <mail@thomasvoss.com>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
+# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# Edit a temporary copy of the doas.conf file and check it for syntax
+# errors before installing it as the actual doas.conf file.
+
+set -eu
+
+PATH=/bin:/usr/bin:/usr/local/bin
+export PATH
+
+PROG="${0##*/}"
+
+umask 022
+
+DOAS_CONF=@DOAS_CONF@
+doas_conf_mode="0600"
+
+warn() { echo "$PROG: $@" >&2; }
+die() { rv=$1; shift; warn "$@"; exit $rv; }
+
+get_intr() {
+ stty -a | sed -En '
+ /^(.* )?intr = / {
+ s///
+ s/;.*$//
+ p
+ }'
+}
+
+set_trap_rm() {
+ local file files
+ files=
+ for file in "$@"; do
+ files="$files '$file'"
+ done
+ [ -n "$files" ] && trap "rm -f $files" EXIT HUP INT TERM
+}
+
+noop=0
+
+while getopts hn c; do
+ case "$c" in
+ n)
+ noop=$(($noop + 1))
+ ;;
+ *)
+ echo "Usage: $PROG [-n] [file]" >&2;
+ exit 1
+ ;;
+ esac
+done
+shift $(($OPTIND - 1))
+
+case $# in
+0) ;;
+1) DOAS_CONF="$1" ;;
+*) usage 1>&2; exit 1 ;;
+esac
+
+case ${noop} in
+0) noop=false ;;
+1) noop=true ;;
+*) noop=true; exec >/dev/null 2>&1 ;;
+esac
+
+case "$DOAS_CONF" in
+-*)
+ warn "Invalid filename: $DOAS_CONF"
+ die 1 "Try using './$DOAS_CONF' instead"
+ ;;
+esac
+
+doas_conf_dir="$(dirname "$DOAS_CONF")"
+doas_conf_base="$(basename "$DOAS_CONF")"
+DOAS_CONF="$doas_conf_dir/$doas_conf_base"
+doas_lock_file="$DOAS_CONF.lck"
+
+# These checks are only for producing nicer diagnostic messages to the
+# user. They are not relied on by the rest of the code.
+
+[ ! -e "$doas_conf_dir" ] && die 1 "$doas_conf_dir does not exist"
+[ ! -d "$doas_conf_dir" ] && die 1 "$doas_conf_dir is not a directory"
+[ ! -w "$doas_conf_dir" ] && {
+ owner="$(stat -c %U "$doas_conf_dir")"
+ warn "$doas_conf_dir is not writable"
+ die 1 "You probably need to run $PROG as $owner"
+}
+
+tmp_doas="$(mktemp --tmpdir vidoas.XXXXXXXXXX)" \
+ || die 1 "You probably need to run $PROG as root"
+set_trap_rm "$tmp_doas"
+
+# It is important that the ln(1) command fails if the target already
+# exists. Some versions are known to behave like "ln -f" by default
+# (removing any existing target). Adjust PATH to avoid such ln(1)
+# implementations.
+
+tmp_test_ln="$(mktemp --tmpdir vidoas.XXXXXXXXXX)"
+set_trap_rm "$tmp_doas" "$tmp_test_ln"
+
+ln "$tmp_doas" "$tmp_test_ln" 2>/dev/null \
+ && die 1 'ln(1) is not safe for creating lock files, bailing'
+
+# If a doas.conf file exists, copy it into the temporary file for
+# editing. If none exist, the editor will open with an empty file.
+
+[ -f "$DOAS_CONF" ] && {
+ if [ -r "$DOAS_CONF" ]; then
+ cp "$DOAS_CONF" "$tmp_doas"
+ else
+ die 1 "$DOAS_CONF is not readable"
+ fi
+}
+
+$noop && {
+ doas -C "$DOAS_CONF" || die 1 "$DOAS_CONF contains syntax errors."
+ die 0 'OK: Prerequisite checks passed'
+}
+
+# Link the temporary file to the lock file.
+
+if ln "$tmp_doas" "$doas_lock_file"; then
+ set_trap_rm "$tmp_doas" "$tmp_test_ln" "$doas_lock_file"
+else
+ die 1 "$DOAS_CONF is already locked"
+fi
+
+# Some versions of vi(1) exit with a code that reflects the number of
+# editing errors made. This is why we ignore the exit code from the
+# editor.
+
+doasedit "$tmp_doas" || true
+
+until doas -C "$tmp_doas"; do
+ warn "Press enter to edit doas.conf again to fix it,"
+ warn "or ($(get_intr)) to cancel."
+ read _
+ doasedit "$tmp_doas" || true
+done
+
+# Use mv(1) to rename the temporary file to doas.conf as it is atomic.
+# Update: No longer use mv as it messes up permissions on the doas.conf file.
+# Use install with ownership set to root.
+
+if [ -s "$tmp_doas" ]; then
+ if cmp -s "$tmp_doas" "$DOAS_CONF"; then
+ warn "No changes made"
+ warn "$DOAS_CONF unchanged"
+ else
+ install -o root -m "$doas_conf_mode" "$tmp_doas" \
+ "$DOAS_CONF" \
+ && warn "$DOAS_CONF updated"
+ fi
+else
+ warn "Not installing an empty doas.conf file"
+ warn "$DOAS_CONF unchanged"
+fi