Joel Beckmeyer's Blog
Links:

- Home
- Contact
- Posts

# Hello doas

Saturday, January 30, 2021

Today, I switched my workstation from `sudo` to `doas`. I’m running Void Linux, and the process was fairly easy.

First, I needed to figure out how to remove `sudo` (yes, I realize I could have installed `doas` first, then removed `sudo`, but I decided to do it the hard way.) As it turns out, the advanced usage section of the XBPS manual details how to use the `ignorepkg` entry in xbps.d with nothing other than this exact use case! I created the file `/etc/xbps.d/20-ignorepkg-sudo.conf` with contents

    ignorepkg=sudo

and then ran `sudo xbps-remove sudo` (an ironic command).

After that, because I was stupid and removed `sudo` before I had set up `doas`, I had to use plain-old `su` to change to the root user and run `xi opendoas`. I also configured `doas` in `/etc/doas.conf` with the following:

    # see doas.conf(5) for configuration details
    permit nopass keepenv :admin

I ran `groupadd admin`, `usermod -aG admin joel`, and then logged out so that my user account would see the new group perms.

And just like that, I can now run `doas xbps-install ...` and all of my other commands, just substituting `doas` for `sudo`.

The one thing I immediately missed was `sudoedit`. Before I accidentally tried to use `sudo` for the first time, I had already accidentally tried to run `sudoedit` at least 5 times. I had to fix this. I saw a discussion on Reddit where one user suggested writing a script to replace the `sudoedit` functionality. I quickly starting hacking together something like that. I started with:

    #!/bin/sh
    mkdir -p /tmp/doasedit
    doas cp $1 /tmp/doasedit/tmp_file
    $EDITOR /tmp/doasedit/tmp_file

And quickly ran into my first road-block. The script is going to have to change the permissions of that file before the user can edit it. But if the script changes the permissions, how can I restore it to the original location with the right permissions? `cp /tmp/doasedit/tmp_file $1` won’t work. I thought about just using cat to overwrite the file contents in-place (`cat /tmp/doasedit/tmp_file > $1`). That could create some issues if a program has the file open. Instead, a better option is to create two copies of the file–one for editing, and one for preserving file attributes:

    #!/bin/sh
    mkdir -p /tmp/doasedit
    doas cp $1 /tmp/doasedit/edit
    doas chown -R $USER:$USER /tmp/doasedit/edit
    doas cp $1 /tmp/doasedit/file
    $EDITOR /tmp/doasedit/edit
    cat /tmp/doasedit/edit | doas tee /tmp/doasedit/file 1>/dev/null
    doas mv -f /tmp/doasedit/file $1
    rm -rf /tmp/doasedit

Of course, the issue with this is that it only works with absolute paths. I want to make it work for relative paths as well. I’m going to take advantage of `realpath`, which is part of the `coreutils` package from Void. As a bonus, this will also take care of the edge case where the given file is a symlink (IIRC, `sudoedit` didn’t follow symlinks, so I may be diverging here):

    #!/bin/sh
    mkdir -p /tmp/doasedit
    srcfile="$(realpath $1)"

    doas cp $srcfile /tmp/doasedit/edit
    doas chown -R $USER:$USER /tmp/doasedit/edit
    doas cp $srcfile /tmp/doasedit/file

    $EDITOR /tmp/doasedit/edit

    cat /tmp/doasedit/edit | doas tee /tmp/doasedit/file 1>/dev/null
    doas mv -f /tmp/doasedit/file $srcfile

    rm -rf /tmp/doasedit

At this point, it works…okay-ish. It can only be used in one instance currently since I hard-coded `/tmp/doasedit/file` and `/tmp/doasedit/edit`, but that’s easily fixed:

    #!/bin/sh

    destfile_pfx="$(cat /dev/urandom | tr -cd 'a-f0-9' | head -c 32)"

    while [ -d "/tmp/doasedit/$destfile_pfx" ]; do
    	destfile_pfx="$(cat /dev/urandom | tr -cd 'a-f0-9' | head -c 32)"
    done

    mkdir -p /tmp/doasedit/$destfile_pfx
    srcfile="$(realpath $1)"

    doas cp $srcfile /tmp/doasedit/$destfile_pfx/edit
    doas chown -R $USER:$USER /tmp/doasedit/$destfile_pfx/edit
    doas cp $srcfile /tmp/doasedit/$destfile_pfx/file

    $EDITOR /tmp/doasedit/$destfile_pfx/edit

    cat /tmp/doasedit/$destfile_pfx/edit | doas tee /tmp/doasedit/$destfile_pfx/file 1>/dev/null
    doas mv -f /tmp/doasedit/$destfile_pfx/file $srcfile

    rm -rf /tmp/doasedit/$destfile_pfx

At this point, the only thing missing is the check to see if the file was actually edited:

    ...
    cat /tmp/doasedit/$destfile_pfx/edit | doas tee /tmp/doasedit/$destfile_pfx/file 1>/dev/null

    if cmp -s "/tmp/doasedit/$destfile_pfx/file" "$srcfile"; then
    	echo "Skipping write; no changes."
    else
    	doas mv -f /tmp/doasedit/$destfile_pfx/file $srcfile
    fi
    ...

I put this in a repo on GitHub if anyone is interested. I know that a major weakness of this script is the number of times it calls `doas`, which could break flows where password is required every time `doas` is run.