vitor - safely edit the Tor configuration file

what if sudoedit is not used (not just it) for editing the tor configuration files but a visudo for tor?
vitor (cough bad name I invented)

It creates a copy of the tor configuration file you want to edit, you open with the editor the copy, you can open with sudoedit which will use the default environment variables for the editor as specified on the sudo manual, but then after closing the editor, run tor -f file --verify-config --hush to see if configuration is valid and tor will not break because of invalid configuration?

Updated script: https://github.com/nyxnor/onionjuggler/blob/main/bin/vitor
Manual page: https://github.com/nyxnor/onionjuggler/blob/main/docs/vitor.8.md

Some changes are ā€œneededā€ for whonix as in change the default configuration to 50_user_conf instead of /etc/tor/torrc, removing doas option and only use sudo, use the default tor user

#!/usr/bin/env sh

## Copy tor config to temp, lock the original file, modify the temp,
## verify it is ok then save back to original place and remove temporary and lock file.

## Inspired by https://github.com/slicer69/doas/blob/master/vidoas


file_mode="644"
me="${0##*/}"

## colors
#nocolor="\033[0m"
#bold="\033[1m"
#nobold="\033[22m"
#underline="\033[4m"
#nounderline="\033[24m"
#red="\033[31m"
#green="\033[32m"
#yellow="\033[33m"
#blue="\033[34m"
#magenta="\033[35m"
#cyan="\033[36m"

## display error message with instructions to use the script correctly.
notice(){ printf %s"${me}: ${1}\n" 1>&2; }
error_msg(){ notice "${1}"; exit 1; }
usage(){ printf '%s\n' "Usage: [sudo|doas] ${me} [-f tor_conf] [-u tor_user]
Note:
  ${me}               run ${me} as the root user with 'sudo' or 'doas'
  -f tor_conf         if 'tor_conf' is not set, default to /etc/tor/torrc
                      if the file doesn't exist, will create it after passing all tests.
  -u tor_user         if 'tor_user' is not set, the tor_conf must contain the \"User\" option
                      else it tor fails to validate the configuration"
  exit 1
}

get_arg(){ case "${2}" in ""|-*) error_msg "Option '${1}' requires an argument.";; esac; }

[ -n "${1}" ] && [ -z "${2}" ] && tor_conf="${1}"
while :; do
  case "${1}" in
    -f) get_arg "${1}" "${2}"; tor_conf="${2}"; shift 2;;
    -u) get_arg "${1}" "${2}"; tor_user="${2}"; shift 2;;
    "") break;;
    *) usage;;
  esac
done

[ -n "${SUDO_USER}" ] && su_cmd="sudo"
[ -n "${DOAS_USER}" ] && su_cmd="doas"
{ [ -z "${su_cmd}" ] || [ "$(id -u)" -ne 0 ]; } && error_msg "Run ${me} as the root user with 'sudo' or 'doas'."

## get editor. First try environment variables [SUDO|DOAS]_EDITOR, if empty try VISUAL, if empty try EDITOR, if empty use Vi
eval PRIVILEGED_EDITOR='$'"$(printf %s"${su_cmd##*/}" | tr '[:lower:]' '[:upper:]')_EDITOR"
editor="${PRIVILEGED_EDITOR:-"${VISUAL:-"${EDITOR:-vi}"}"}"
## get interrupt signal
#get_intr="$(stty -a | sed -n '/.*intr = / {s///;s/;.*$//;p;}')"

## get first argument, if empty, onionjuggler.conf variable, if empty, fallback to default torrc
file="${tor_conf:-"/etc/tor/torrc"}"
## remove last backlash if inserted by mistake
file="${file%*/}"

if [ -f "${file}" ]; then
  tor_user_check="$(grep "^User" "${file}" | sed "s/^User //")"
  if [ -n "${tor_user_check}" ] && [ -n "${tor_user}" ] && [ "${tor_user_check}" != "${tor_user}" ]; then
    notice "The tor configuration file contains the user ${tor_user_check}, but you specified the tor user ${tor_user}."
    error_msg "Are you running tor as the correct user?"
  fi
  if [ -z "${tor_user_check}" ] && [ -z "${tor_user}" ]; then
    notice "\"User\" option is not set on the tor configuration file nor in the command line."
    notice "Specify the tor user with:"
    error_msg "$ ${me} -u tor_user"
  fi
fi


## get just the directory
file_dir="${file%/*}"

## check permissions
[ ! -d "${file_dir}" ] && error_msg "${file_dir} is not a directory or doesnt exist."
[ ! -w "${file_dir}" ] && error_msg "${file_dir} is not writable by ${USER}."
[ ! -r "${file_dir}" ] && error_msg "${file_dir} is not readble by ${USER}."

## file is the first argument, replace '.' and '-' for '_'.
file_name="$(printf %s"${file##*/}" | tr "." "_" | tr "-" "_")"
file_name_tmp="$(printf %s"mkstemp(${file_dir}/${file_name}.XXXXXX)" | m4)"
chmod "${file_mode}" "${file_name_tmp}"

file_locked="${file}.lck"
## test if file is already in use.
ln "${file}" "${file_locked}" || error_msg "${file} is busy, try again later."

if [ -f "${file}" ]; then
  [ ! -r "${file}" ] && error_msg "${file} is not readable."
  [ ! -w "${file}" ] && error_msg "${file} is not writable."
  cp -p "${file}" "${file_name_tmp}"
fi

check_tmp_tor_user(){
  tor_user_check="$(grep "^User" "${file_name_tmp}" | sed "s/^User //")"
  if [ -n "${tor_user_check}" ]; then
    su_tor_cmd="${su_cmd}"
  else
    if [ -z "${tor_user}" ]; then
      notice "You probably commented or deleted the \"User\" option, but you can only do that if you specify the tor user."
      notice "If that is what you want, exit and run:"
      notice "$ ${me} -u tor_user"
    else
      su_tor_cmd="${su_cmd} -u ${tor_user}"
    fi
  fi
}

trap '' INT
trap 'rm -f ${file_name_tmp} ${file_locked}' EXIT

## open temporary file to be edited
"${editor}" "${file_name_tmp}" || true
check_tmp_tor_user

## while the config is not ok, loop to enter and continue to edit or signal to interrupt.
while ! ${su_tor_cmd} tor -f "${file_name_tmp}" --verify-config --hush; do
  tor_check="$(${su_tor_cmd} tor -f "${file_name_tmp}" --verify-config --hush)"
  printf '%s\n' "${tor_check}" | grep -q "Permission denied" && notice "Got permission denied to read tor directories, did you specify the tor user? If yes, maybe the directories do not have the right owner."
  printf '%s\n' "${tor_check}" | grep -q "You are running Tor as root. You don't need to, and you probably shouldn't." && notice "Do not run tor as root if you did not set the \"User\" option."
  notice "The temporary copy on ${file_name_tmp} is not a valid configuration."
  notice "Options are:"
  notice "  (e)enter to edit again."
  notice "  e(x)it to cancel without saving changes."
  while :; do
  printf %s"${me}: Your choice: "
    # shellcheck disable=SC2034
    read -r status
    case "${status}" in
      e|E) break;;
      x|X) exit;;
    esac
  done
  "${editor}" "${file_name_tmp}" || true
  check_tmp_tor_user
done

! cmp -s "${file_name_tmp}" "${file}" && cp -p "${file_name_tmp}" "${file}" && notice "${file} updated." && exit 0
1 Like

Could you try to contribute this upstream please to The Tor Project or Debian?

I opened one issue to TPO to see if this sounds interesting to them and asked if they could implment in C to be more tight.

1 Like

I could make vitor a debian package just like did with tor-ctrl, not gonna be in the debian repo of course, but would separe vitor script to its own repo.

Vitor is now possible to use on the Workstation because --verify-config was allowed here implement `tor --verify-config` Ā· adrelanos/anon-ws-disable-stacked-tor@7a1e0b5 Ā· GitHub

and -f combined with --verify-config was allowed here Tor emulation: fix, pass all command line options to `tor` when beingā€¦ Ā· adrelanos/anon-ws-disable-stacked-tor@e6c05d9 Ā· GitHub

(ā€“verify-config needs to be before -f FILE, for this to work, which will need to correct on vitor or maybe on tor.anondist.)

anyway, donā€™t try it yet, or if you dare, donā€™t blame me (but the there are some important bugs to fix).

1 Like

feature requests:

Why not simply all of this refer to sudoedit?
Then you wouldnā€™t need any editor auto detection code as well manual file copy to temporary location and copying back.

Whonix ships also gsudoedit.

Any chance to move this to the tor-ctrl package instead? Because then it would be easier to pre-install in Whonix.

onionjuggler dunno but I guess itā€™s not (yet) compatible with Whonix, doing something more specialized and might be harder to make compatible with Whonix.

Separate package for vitor is probably too much. 1 file = 1 package has very little chances of inclusion into packages.debian.org due to overhead per package. Iā€™ve read somewhere at Debian ā€œit has to be justifiedā€.

that can be done I just donā€™t see how the files are related in any way but ok.

onionjuggler dunno but I guess itā€™s not (yet) compatible with Whonix, doing something more specialized and might be harder to make compatible with Whonix.

OnionJuggler manages HiddenServiceDir, HiddenServicePort and HiddenServiceVersion, as well as ClientOnionAuthDir. It was initially built for Debian but it has become system agnostic, using the configuration file for your own operating system. Anyway, Vitor is just a single script of it and putting in a separate repo other than OnionJuggler (will be on tor-ctrl) wonā€™t affect negatively. But as Iā€™ve seen happening with tor-ctrl, being specialized in Whonix is a whole work for it.

Separate package for vitor is probably too much. 1 file = 1 package has very little chances of inclusion into packages.debian.org due to overhead per package. Iā€™ve read somewhere at Debian ā€œit has to be justifiedā€.

I was thinking of just having that package for personal use and Whonix, but if still better to put on packages.debian.org, than later could be done after the script is good enough.

1 Like

Because I was running it on OpenBSD and they use doas there, which does not come with doasedit, but can be installed from a third party.

But I can detect if sudoedit or doasedit is installed and use it.

1 Like

the problem with using gsudoedit, sudoedit and doasedit is that the copy of the file is deleted as soon as the editor is closed.

vitor mini architecture:

input file to be edited or fallback tor /etc/tor/torrc. Whonix could make a wrapper to call vitor -f /usr/local/bin/etc/tor/50_user.conf.

then create a copy of this file, symlink it to a lock file. This prevents vitor trying to open the same file on another session at the same time the file is being edited.

then it opens the file with your preferred editor,

after closing the editor, the file is verified with tor options. But if the temporary file is removed as it happens with gsudoedit, sudoedit, doasedit, it is not possible to verify the temporary file cause this programs save the file directly without warning, only if they were changed.

I need to remove the possibility to use these wrappers as it will fail. Anyway, using environment variables is the way for anyone to select their preferred editor, eve if is mousepad if installed.

1 Like

I created a separate repo because it has nothing to do with tor-ctrl, it is for editing the torrc or any other tor configuration file (excluding files inside HiddenServiceDir/authorized_clients and ClientOnionAuthDir or course). Just the tor run commands file.

1 Like

This would become:

lxsudo sudo -u "$(whoami)" env VISUAL="$VISUAL" vitor -u debian-tor /usr/local/etc/torrc.d/50_user.conf

demo video

1 Like

How would it work if started from start menu if there is no terminal output available?

1 Like

I see the problem now if no terminal is opened, can you instruct on how to do a gui like application such as GitHub - adrelanos/sdwdate-gui: Grapical User Interface (gui), Systray Icon for for sdwdate for this case? I will read the code nonetheless.

This will lead to two repos, one with gui and other without as it happens on sdwdate.

1 Like

I am not much of a GUI developer. I can maintain / bugfix things but I think didnā€™t invent a sophisticated GUI from scratch myself yet. The GUI applications by Whonix where contributed by other developers.

add anon-verify and torrc-parser. Anon-info maybe because it is only for debian hosts, or include on installation if debian system is detected.

rename vitor to a broader name to serve tor configuration files checker/parser/verifier.

Adrelanos, what is the reason for a part of the script to be in /usr/libexec?

https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch04s07.html

1 Like

Have not commited changes to github, but I am discussing with myself there.
Some fixes being done, if any new feature is wanted, just open an issue.

1 Like

Specifically why /usr/libexec and not some other folder? If some other folder, which one? Debianā€™s lintian complained about /usr/lib being the wrong location.

Or why is it split into two folders? Not sure what @iry had in mind. Perhaps sourceing that script from other scripts but that didnā€™t happen yet. So could also be all in 1 file, I guess.

1 Like

Asked on IRC if TPO has interest in producing a vitor, visudo for tor, if they reply something, I will post here, if not, then at least I tried.

1 Like

To keep vitor safe, I was studying some of sudoedit and visudo security features. I donā€™t know if I can tackle then all but some of vitor functionality and features are based on this tools for safety, as I am more than sure those developers have spent much more time hardening over the years than vitor has with its few months.

Sudoedit

 To help prevent the editing of unauthorized files, the folā€
 lowing restrictions are enforced unless explicitly allowed by
 the security policy:

Letā€™s take it bit by bit.

ā€¢ Symbolic links may not be edited (version 1.8.15 and
higher).

The file could be not created yet, but if the file exists -e, then vitor should test if it is a regular file -f.

ā€¢ Symbolic links along the path to be edited are not folā€
lowed when the parent directory is writable by the invokā€
ing user unless that user is root (version 1.8.16 and
higher).

Isnā€™t symlinks going to be blocked anyway?

ā€¢ Files located in a directory that is writable by the inā€
voking user may not be edited unless that user is root
(version 1.8.16 and higher).

Why is this a security feature on sudoedit? What does it protect? Using privileges when not needed? The torrc is owned either by root or debian-tor, so you will need privilege. But if you are in the tor group, this feature is to guard against an attack exploiting the editor you are using with shell escapes? Anyway, using sudo/doas to copy the file and the normal user to edit the file.

Note that unlike most commands run by sudo, the editor is run
with the invoking userā€™s environment unmodified.

Done in vitor.

If the temporary file becomes empty after editing, the user will be
prompted before it is installed.

Not done, I donā€™t see a reason, maybe this is my pov.

If, for some reason, sudo is unable to update a file with its edited version, the user will receive a warning and the edited copy will remain in a temporary file.

Done in vitor.

visudo

In addition to reporting sudoers syntax errors, visudo may produce the
following messages:

sudoers file busy, try again later.
     Someone else is currently editing the sudoers file.

This is done in vitor, more like a info feature than a security feature.
As the file name may vary because of mktemp, the file.lck is displayed so you can know the path to delete the lock if you know it is a bug.

1 Like