diff options
Diffstat (limited to 'home/.local/bin/doasedit')
| -rwxr-xr-x | home/.local/bin/doasedit | 202 | 
1 files changed, 202 insertions, 0 deletions
| diff --git a/home/.local/bin/doasedit b/home/.local/bin/doasedit new file mode 100755 index 0000000..a3bdb2d --- /dev/null +++ b/home/.local/bin/doasedit @@ -0,0 +1,202 @@ +#!/bin/sh -e + +help() { +  cat - >&2 <<EOF +doasedit - edit non-user-editable files with an unprivileged editor + +usage: doasedit -h | -V +usage: doasedit file ... + +Options: +  -h, --help     display help message and exit +  -V, --version  display version information and exit +  --             stop processing command line arguments + +Environment Variables: +  DOAS_EDITOR    program used to edit files +  EDITOR         program used to edit files if DOAS_EDITOR is unset + +To work properly doasedit needs to always start a new editor instance. Some +editors, graphical ones in particular, open files in previously running +instances. If so, append a command line argument to your (DOAS_)EDITOR variable +such that the editor will always start a new instance (e. g.: 'kate -n'). + +How it works: +Every File to be edited is duplicated to a user owned file in /tmp. The editor +is then run in user context. After closing the editor the user file replaces +the original file while preserving file attributes. All this is done using doas +as little as possible. Files are edited one after another, not all at once. +EOF +} + +# Checks for syntax errors in doas' config +# +# check_doas_conf <target> <tmp_target> +# +check_doas_conf() { +  if printf '%s' "${1}" | grep -q '^/etc/doas\(\.d/.*\)\?\.conf$'; then +    while ! doas -C "${2}"; do +      printf "doasedit: Replacing '%s' would " "$file" +      printf 'introduce the above error and break doas.\n' +      printf '(E)dit again, (O)verwrite anyway, (A)bort: [E/o/a]? ' +      read -r choice +      case "$choice" in +      o | O) +        return 0 +        ;; +      a | A) +        return 1 +        ;; +      e | E | *) +        "$editor_cmd" "$tmpfile" +        ;; +      esac +    done +  fi +  return 0 +} + +error() { +  printf 'doasedit: %s\n' "${@}" 1>&2 +} + +_exit() { +  rm -rf "$tmpdir" +  trap - EXIT HUP QUIT TERM INT ABRT +  exit "${1:-0}" +} + +# no argument passed +[ "${#}" -eq 0 ] && help && exit 1 + +while [ "${#}" -ne 0 ]; do +  case "${1}" in +  --) +    shift +    break +    ;; +  --help | -h) +    help +    exit 0 +    ;; +  --version | -V) +    printf 'doasedit version 1.0.7\n' +    exit 0 +    ;; +  -*) +    printf "doasedit: invalid option: '%s'\n" "${1}" +    help +    exit 1 +    ;; +  *) +    break +    ;; +  esac +done + +[ "$DOAS_EDITOR" != "" ] && editor_cmd="$DOAS_EDITOR" || editor_cmd="$EDITOR" +# shellcheck disable=SC2086 +if [ "$editor_cmd" = "" ]; then +  if command -v vi >/dev/null 2>&1; then +    editor_cmd='vi' +  else +    error 'no editor specified' +    exit 1 +  fi +elif ! command -v "$editor_cmd" >/dev/null 2>&1; then +  error "invalid editor command: '${editor_cmd}'" +  exit 1 +fi + +exit_code=1 +trap '_exit "${exit_code}"' EXIT +trap '_exit 130' HUP QUIT TERM INT ABRT +tmpdir="$(mktemp -dt 'doasedit-XXXXXX')" + +for file; do +  unset exists readable writable +  dir="$(dirname -- "$file")" +  tmpfile="${tmpdir}/${file##*/}" +  tmpfile_copy="${tmpdir}/copy-of-${file##*/}" +  printf '' | tee "$tmpfile" >"$tmpfile_copy" +  chmod 0600 "$tmpfile" "$tmpfile_copy" + +  if [ -e "$file" ]; then +    if ! [ -f "$file" ]; then +      error "${file}: not a regular file" +      continue +    fi +    # -O is not POSIX, but implemented at least in GNU, *BSD and macOS test +    if [ -O "$file" ]; then +      error "${file}: editing your own files is not permitted" +      continue +    fi +    exists=1 +  elif doas [ -e "$file" ]; then +    if ! doas [ -f "$file" ]; then +      error "${file}: not a regular file" +      continue +    fi +    exists=0 +  else +    # New file? +    if [ -O "$dir" ]; then +      error "${file}: creating files in your own directory is not permitted" +      continue +    elif [ -x "$dir" ] && [ -w "$dir" ]; then +      error "${file}: creating files in a user-writable directory is not permitted" +      continue +    elif ! doas [ -e "$dir" ]; then +      error "${file}: no such directory" +      continue +    # else: root-writable directory +    fi +  fi +  # If this test is true, it's an existent regular file +  if [ "$exists" != "" ]; then +    if [ -w "$file" ]; then +      writable=1 +    # Check in advance to make sure that it won't fail after editing. +    elif ! doas dd status=none count=0 of=/dev/null; then +      error "unable to run 'doas dd'" +      continue +    fi +    if [ -r "$file" ]; then +      if [ "$writable" != "" ]; then +        error "${file}: editing user-readable and -writable files is not permitted" +        continue +      fi +      # Read file +      cat -- "$file" >"$tmpfile" +    # Better not suppress stderr here as there might be something of importance. +    elif ! doas cat -- "$file" >"$tmpfile"; then +      error "you are not permitted to call 'doas cat'" +      continue +    fi +    cat "$tmpfile" >"$tmpfile_copy" +  fi + +  "$editor_cmd" "$tmpfile" + +  check_doas_conf "$file" "$tmpfile" || continue +  if cmp -s "$tmpfile" "$tmpfile_copy"; then +    printf 'doasedit: %s: unchanged\n' "$file" +  else +    if [ "$writable" != "" ]; then +      dd status=none if="$tmpfile" of="$file" +    else +      for de_tries in 2 1 0; do +        if doas dd status=none if="$tmpfile" of="$file"; then +          break +        elif [ "$de_tries" -eq 0 ]; then +          error '3 incorrect password attempts' +          exit 1 +        fi +      done +    fi +  fi + +  exit_code=0 +done + +# vim: shiftwidth=2 tabstop=2 noexpandtab | 
