aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/home/.local/bin/doasedit
diff options
context:
space:
mode:
authorLibravatar sommerfeld <sommerfeld@sommerfeld.dev>2025-09-11 16:38:11 +0100
committerLibravatar sommerfeld <sommerfeld@sommerfeld.dev>2025-09-11 16:38:11 +0100
commitaa62e1f27b0cb3d712d6f2b13071cca0f09379be (patch)
treea6d2a4ba09d66490bc7c1a23f35be707fa0f5c8b /home/.local/bin/doasedit
parentc1f310bcc39f6cf4684d938d7be45bb25b427335 (diff)
downloaddotfiles-master.tar.gz
dotfiles-master.tar.bz2
dotfiles-master.zip
Add a lot of changesHEADmaster
Diffstat (limited to 'home/.local/bin/doasedit')
-rwxr-xr-xhome/.local/bin/doasedit202
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