diff options
Diffstat (limited to 'vidoas')
-rwxr-xr-x | vidoas | 173 |
1 files changed, 173 insertions, 0 deletions
@@ -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 |