#!/bin/sh # Copyright (c) 2020 Kimmo Suominen # Copyright (c) 2022 Thomas Voss # # 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" [ $(id -u) -eq 0 ] && EDIT="${VISUAL:-${EDITOR:-vi}}" || EDIT=doasedit warn() { echo "$PROG: $@" >&2; } die() { rv=$1; shift; warn "$@"; exit $rv; } usage() { die 1 "Usage: $PROG [-n] [file]"; } 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 n c; do case "$c" in n) noop=$(($noop + 1)) ;; *) usage ;; esac done shift $(($OPTIND - 1)) case $# in 0) ;; 1) DOAS_CONF="$1" ;; *) usage ;; 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.lock" # 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. "$EDIT" "$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 _ "$EDIT" "$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