#!/sbin/openrc-run # Copyright 1999-2021 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 CHROOT_DIR=${CHROOT_DIR:-/chroot/tor} CHROOT_DIR_CHECK=${CHROOT_DIR_CHECK:-true} CHROOT_EXEC=${CHROOT_EXEC:-/bin/chroot} CHROOT_JCHROOT=${CHROOT_JCHROOT:-false} CHROOT_JCHROOT_EXEC=${CHROOT_JCHROOT_EXEC:-/usr/bin/jchroot} CHROOT_JCHROOT_NEW_USER_NS=${CHROOT_JCHROOT_NEW_USER_NS:-false} CHROOT_JCHROOT_NEW_USER_NS_CHECK_KERNEL=${CHROOT_JCHROOT_NEW_USER_NS_CHECK_KERNEL:-true} CHROOT_MOUNT_CHECK_TIMEOUT=${CHROOT_MOUNT_CHECK_TIMEOUT:-20} CHROOT_REFRESH_BINARIES=${CHROOT_REFRESH_BINARIES:-false} CHROOT_TOR_DATA_DIR=${CHROOT_TOR_DATA_DIR:-/var/lib/tor/data} CHROOT_TOR_DATA_DIR_BIND=${CHROOT_TOR_DATA_DIR_BIND:-false} CHROOT_TOR_HOME_DIR=${CHROOT_TOR_HOME_DIR:-/var/lib/tor} CHROOT_TOR_HOME_DIR_BIND=${CHROOT_TOR_HOME_DIR_BIND:-true} CHROOT_TOR_RC=${CHROOT_TOR_RC:-/etc/tor/torrc} CHROOT_TOR_RC_UPDATE=${CHROOT_TOR_RC_UPDATE:-true} CHROOT_UPDATE_ETC_FILES=${CHROOT_UPDATE_ETC_FILES:-true} MOUNTS_FILE=/run/${RC_SVCNAME}.mounts PID_FILE=/run/${RC_SVCNAME}.pid STOP_TIMEOUT=${STOP_TIMEOUT:-60} TOR_DATA_DIR=${TOR_DATA_DIR:-/var/lib/tor/data} TOR_GROUP=${TOR_GROUP:-tor} TOR_HOME_DIR=${TOR_HOME_DIR:-/var/lib/tor} TOR_RC=${TOR_RC:-${CONFIG_FILE:-/etc/tor/torrc}} TOR_USER=${TOR_USER:-tor} VERBOSE=${VERBOSE:-false} VERBOSE_START_STOP_DAEMON=${VERBOSE_START_STOP_DAEMON:-false} if [ "${VERBOSE}" = true ]; then export EINFO_VERBOSE=true else unset EINFO_VERBOSE fi description="Anonymizing overlay network for TCP (Chroot Mode)" description_checkconfig="Check if ${TOR_RC} is valid" description_reload="Reload the configuration" description_setup="Set up chroot directory" extra_commands="checkconfig setup" extra_started_commands="reload" depend() { need net } _check_chroot_dir_value() { if [ -z "${CHROOT_DIR}" ]; then "Please set a value for 'CHROOT_DIR' in '/etc/conf.d/${RC_SVCNAME}'." return 1 elif [ "${CHROOT_DIR}" = / ]; then eerror "Root directory can't be set as chroot directory." return 1 fi return 0 } _call() { ebegin "Running '$*'" "$@" eend "$?" || exit 1 } _die() { eerror "$1" exit 1 } _non_verbose_eerror() { [ -z "${EINFO_VERBOSE}" ] && eerror "$1" return 1 } _mknod() { local target mode target=$1 mode=$2 shift 2 if [ -e "${target}" ]; then if [ -c "${target}" ]; then ewarn "Skipping creation of ${target} as it already exists." return 0 else eerror "${target} exists but is not a device node." return 1 fi else _call mknod -m "${mode}" "${target}" "$@" fi } _generate_rcopy_targets() { set -- /etc/ld.so.conf* [ "$1" = '/etc/ld.so.conf*' ] && set -- printf '%s\n' /usr/bin/tor /sbin/ldconfig "$@" qlist -eC sys-devel/gcc | grep libgcc_s qlist -eC sys-libs/glibc | grep libnss_compat } setup() { _check_chroot_dir_value || return 1 if [ -d "${CHROOT_DIR}" ]; then ewarn "${CHROOT_DIR} already exists and some things might be overridden." ewarn "Press CTRL+C within 10 seconds if you don't want to continue." sleep 10 einfo "" fi ebegin "Setting up the chroot directory" local __ for __ in '' /dev "/etc/tor" "${CHROOT_TOR_HOME_DIR}" "${CHROOT_TOR_DATA_DIR}" /var/tmp /tmp; do _call mkdir -p "${CHROOT_DIR}$__" done _call chown "${TOR_USER}:${TOR_GROUP}" "${CHROOT_DIR}/etc/tor" "${CHROOT_DIR}${CHROOT_TOR_HOME_DIR}" "${CHROOT_DIR}${CHROOT_TOR_DATA_DIR}" _call chmod 0750 "${CHROOT_DIR}/etc/tor" "${CHROOT_DIR}${CHROOT_TOR_HOME_DIR}" "${CHROOT_DIR}${CHROOT_TOR_DATA_DIR}" _call chmod 0777 "${CHROOT_DIR}/var/tmp" "${CHROOT_DIR}/tmp" grep --color=never "^${TOR_USER}:" /etc/passwd > "${CHROOT_DIR}"/etc/passwd || _die "Failed to create or update ${CHROOT_DIR}/etc/passwd." grep --color=never "^${TOR_GROUP}:" /etc/group > "${CHROOT_DIR}"/etc/group || _die "Failed to create or update ${CHROOT_DIR}/etc/group." _mknod "${CHROOT_DIR}"/dev/null 666 c 1 3 || return 1 _mknod "${CHROOT_DIR}"/dev/random 644 c 1 8 || return 1 _mknod "${CHROOT_DIR}"/dev/urandom 644 c 1 9 || return 1 ebegin "Copying files using rcopy" _generate_rcopy_targets | xargs rcopy -t "${CHROOT_DIR}" -- || { eerror "Rcopy failed." return 1 } ebegin "Running ${CHROOT_DIR}/sbin/ldconfig" chroot "${CHROOT_DIR}" /sbin/ldconfig eend "$?" || return 1 ebegin "Removing ${CHROOT_DIR}/sbin/ldconfig" rm "${CHROOT_DIR}/sbin/ldconfig" eend "$?" || return 1 einfo "Done." einfo "" einfo "If you use syslog, you may need to manually configure ${CHROOT_DIR}/dev/log." einfo "See /etc/tor/torrc.notes." } _check_tor_rc() { local file="$1" home_dir="$2" data_dir="$3" quiet_mode="$4" ebegin "Checking if configuration file '${file}' is valid" if [ ! -f "${file}" ]; then eend 1 eerror "Tor configuration file '${file}' does not exist." eerror "Example is in /etc/tor/torrc.sample." return 1 fi ( [ "${quiet_mode}" = true ] && exec >/dev/null 2>&1 export HOME="${home_dir}" exec /usr/bin/tor --verify-config -f "${file}" --User "${TOR_USER}" --DataDirectory "${data_dir}" ) || { eend 1 eerror "File failed with 'tor --verify-config' test." eerror "Complete command: HOME='${home_dir}' /usr/bin/tor --verify-config -f '${file}' --User '${TOR_USER}' --DataDirectory '${data_dir}'" return 1 } eend 0 } checkconfig() { if [ "${CHROOT_TOR_RC_UPDATE}" = true ]; then _check_tor_rc "${TOR_RC}" "${CHROOT_DIR}${CHROOT_TOR_HOME_DIR}" "${CHROOT_DIR}${CHROOT_TOR_DATA_DIR}" false || return 1 else _check_tor_rc "${CHROOT_DIR}${CHROOT_TOR_RC}" "${CHROOT_DIR}${CHROOT_TOR_HOME_DIR}" "${CHROOT_DIR}${CHROOT_TOR_DATA_DIR}" false || return 1 fi } _check_node() { local node="$1" type="$2" minor="$3" major="$4" if [ ! -"${type}" "${node}" ] || [ ! "$(stat -c '%t %T' "${node}")" = "${minor} ${major}" ]; then eerror "Device node ${node} does not exist or is not valid." return 1 fi return 0 } _check_chroot() { local __ for __ in '' /dev /usr/bin /var/lib; do checkpath -d -m 0755 -o 0:0 "${CHROOT_DIR}$__" || return 1 done for __ in /etc/tor "${CHROOT_TOR_HOME_DIR}" "${CHROOT_TOR_DATA_DIR}"; do checkpath -d -m 0750 -o "${TOR_USER}:${TOR_GROUP}" "${CHROOT_DIR}$__" || return 1 done if [ -f "${CHROOT_DIR}/etc/tor/torrc" ]; then checkpath -f -m 0750 -o "${TOR_USER}:${TOR_GROUP}" "${CHROOT_DIR}/etc/tor/torrc" || return 1 fi for __ in /var/tmp /tmp; do checkpath -d -m 0777 -o 0:0 "${CHROOT_DIR}$__" || return 1 done _check_node "${CHROOT_DIR}"/dev/null c 1 3 || return 1 _check_node "${CHROOT_DIR}"/dev/random c 1 8 || return 1 _check_node "${CHROOT_DIR}"/dev/urandom c 1 9 || return 1 return 0 } _mountpoint() { awk '$2 == mp { r = 0; exit 0 } END { exit r }' mp="$(readlink -f "$1")" r=1 /proc/mounts } _mount() { if [ "$#" -lt 3 ]; then eerror "_mount: Too few arguments." return 1 fi local from="$1" to="$2" read_only=false __ shift 2 if [ ! -d "${to}" ]; then ebegin "Creating directory ${to}" mkdir -p "${to}" eend "$?" || return 1 fi for __; do case $__ in -r|--read-only) read_only=true ;; esac done _mountpoint "${to}" && { eerror "Unexpected mount detected. Please inspect and unmount ${to}." return 1 } if [ "${read_only}" = true ]; then ebegin "Mounting ${from} to ${to} (RO)" else ebegin "Mounting ${from} to ${to}" fi mount "$@" "${from}" "${to}" eend "$?" || return 1 vebegin "Writing '${to}' to ${MOUNTS_FILE}" echo "${to}" >> "${MOUNTS_FILE}" || _non_verbose_eerror "Failed to write '${to}' to ${MOUNTS_FILE}" veend "$?" } _wait_no_use_dir() { local dir="$1" if fuser -s "${dir}" 2>/dev/null; then ebegin "Waiting until all processes stops using ${dir} (max. ${CHROOT_MOUNT_CHECK_TIMEOUT} seconds)" local waited=0 while sleep 1 waited=$(( waited + 1 )) fuser -s "${dir}" 2>/dev/null do if [ "${waited}" -eq "${CHROOT_MOUNT_CHECK_TIMEOUT}" ]; then eend 1 return 1 fi done eend 0 fi return 0 } _umount() { local dir="$1" _mountpoint "${dir}" || return 0 ebegin "Unmounting ${dir}" umount "${dir}" eend "$?" } _unmount_mounts() { if [ -e "${MOUNTS_FILE}" ]; then ( exec 3< "${MOUNTS_FILE}" || _die "Failed to open $__." wait_no_use=false [ "$1" == --wait-no-use ] && wait_no_use=true while read __ <&3; do [ "${wait_no_use}" = false ] || _wait_no_use_dir "$__" || return 1 _umount "$__" || return 1 done exec 3<&- ) vebegin "Removing ${MOUNTS_FILE}" rm -- "${MOUNTS_FILE}" || _non_verbose_eerror "Failed to remove ${MOUNTS_FILE}" veend "$?" fi } _update_torrc() { ebegin "Updating ${CHROOT_DIR}${CHROOT_TOR_RC} based on ${TOR_RC}" awk '$1 != "User"' "${TOR_RC}" > "${CHROOT_DIR}${CHROOT_TOR_RC}" eend "$?" } _start_stop_daemon() { ( if [ "${VERBOSE_START_STOP_DAEMON}" = true ]; then export EINFO_VERBOSE=true exec start-stop-daemon --verbose "$@" else unset EINFO_VERBOSE exec start-stop-daemon "$@" fi ) } start() { _check_chroot_dir_value || return 1 _unmount_mounts || return 1 if [ -e "${CHROOT_DIR}" ] && fuser -s "${CHROOT_DIR}"; then eerror "Some processes are still accessing ${CHROOT_DIR}." return 1 fi if [ "${CHROOT_DIR_CHECK}" = true ]; then _check_chroot || { eerror "Your chroot directory '${CHROOT_DIR}' is inconsistent." eerror "Please run '/etc/init.d/${RC_SVCNAME} setup' first." return 1 } fi if [ "${CHROOT_TOR_RC_UPDATE}" = true ]; then _check_tor_rc "${TOR_RC}" "${CHROOT_DIR}${CHROOT_TOR_HOME_DIR}" \ "${CHROOT_DIR}${CHROOT_TOR_DATA_DIR}" true || return 1 else _check_tor_rc "${CHROOT_DIR}${CHROOT_TOR_RC}" "${CHROOT_DIR}${CHROOT_TOR_HOME_DIR}" \ "${CHROOT_DIR}${CHROOT_TOR_DATA_DIR}" true || return 1 fi if [ "${CHROOT_REFRESH_BINARIES}" = true ]; then ebegin "Re-copying binaries to ${CHROOT_DIR}" local qopt='-q' [ "${VERBOSE}" = true ] && qopt= _generate_rcopy_targets | xargs rcopy ${qopt} -t "${CHROOT_DIR}" -- || { eerror "Rcopy failed." return 1 } [ "${VERBOSE}" = false ] && eend 0 ebegin "Running ${CHROOT_DIR}/sbin/ldconfig" chroot "${CHROOT_DIR}" /sbin/ldconfig eend "$?" || return 1 ebegin "Removing ${CHROOT_DIR}/sbin/ldconfig" rm "${CHROOT_DIR}/sbin/ldconfig" eend "$?" || return 1 fi if [ "${CHROOT_UPDATE_ETC_FILES}" = true ]; then ebegin "Updating /etc files in ${CHROOT_DIR}" for __ in hosts host.conf localtime nsswitch.conf resolv.conf; do vebegin "Copying /etc/$__ to ${CHROOT_DIR}/etc/" cp -- "/etc/$__" "${CHROOT_DIR}/etc/" || _non_verbose_eerror "Failed to copy /etc/$__ to ${CHROOT_DIR}/etc/." veend "$?" || return 1 done if [ "${CHROOT_JCHROOT}" = false ] || [ "${CHROOT_JCHROOT_NEW_USER_NS}" = false ]; then vebegin "Updating ${CHROOT_DIR}/etc/passwd with 'grep --color=never ${TOR_USER}: /etc/passwd'" grep --color=never "${TOR_USER}:" /etc/passwd > "${CHROOT_DIR}/etc/passwd" || _non_verbose_eerror "Failed to update ${CHROOT_DIR}/etc/passwd using 'grep --color=never ${TOR_USER}: /etc/passwd'." veend "$?" || return 1 vebegin "Updating ${CHROOT_DIR}/etc/group with 'grep --color=never ${TOR_GROUP}: /etc/group'" grep --color=never "${TOR_GROUP}:" /etc/group > "${CHROOT_DIR}/etc/group" || _non_verbose_eerror "Failed to update ${CHROOT_DIR}/etc/group using 'grep --color=never ${TOR_GROUP}: /etc/group'." veend "$?" || return 1 fi [ "${VERBOSE}" = false ] && eend 0 fi if [ "${CHROOT_TOR_RC_UPDATE}" = true ]; then _update_torrc || return 1 fi if [ "${CHROOT_TOR_HOME_DIR_BIND}" = true ]; then _mount "${TOR_HOME_DIR}" "${CHROOT_DIR}${CHROOT_TOR_HOME_DIR}" -o bind || return 1 fi if [ "${CHROOT_TOR_DATA_DIR_BIND}" = true ]; then _mount "${TOR_DATA_DIR}" "${CHROOT_DIR}${CHROOT_TOR_DATA_DIR}" -o bind || { _unmount_mounts; return 1; } fi if [ "${CHROOT_JCHROOT}" = true ]; then if [ "${CHROOT_JCHROOT_NEW_USER_NS}" = true ]; then if [ "${CHROOT_JCHROOT_NEW_USER_NS_CHECK_KERNEL}" = true ] && [ -r /proc/config.gz ]; then zcat /proc/config.gz | grep -q '^CONFIG_USER_NS=[yY]' >/dev/null 2>&1 || \ ewarn "Warning: Kernel doesn't seem configured with CONFIG_USER_NS." fi vebegin "Initializing PID file ${PID_FILE}" : > "${PID_FILE}" && chown "${TOR_USER}:${TOR_GROUP}" "${PID_FILE}" || _non_verbose_eerror "Failed to initialize PID file {PID_FILE}" veend "$?" || return 1 ebegin "Starting Tor (chroot mode using '${CHROOT_JCHROOT_EXEC} --new-user-ns')" _start_stop_daemon \ --start \ --background \ --env HOME="${CHROOT_TOR_HOME_DIR}" \ --user="${TOR_USER}" \ --group="${TOR_GROUP}" \ -- "${CHROOT_JCHROOT_EXEC}" \ --new-user-ns \ --pidfile="${PID_FILE}" \ "${CHROOT_DIR}" \ -- /usr/bin/tor \ -f "${CHROOT_TOR_RC}" \ --DataDirectory "${CHROOT_TOR_DATA_DIR}" else ebegin "Starting Tor (chroot mode using '${CHROOT_JCHROOT_EXEC}')" _start_stop_daemon --start --background \ -- "${CHROOT_JCHROOT_EXEC}" \ --user="${TOR_USER}" \ --group="${TOR_GROUP}" \ --env HOME="${CHROOT_TOR_HOME_DIR}" \ --pidfile="${PID_FILE}" "${CHROOT_DIR}" \ -- /usr/bin/tor \ -f "${CHROOT_TOR_RC}" \ --DataDirectory "${CHROOT_TOR_DATA_DIR}" fi else ebegin "Starting Tor (chroot mode using '${CHROOT_EXEC}')" _start_stop_daemon --start --background \ --make-pidfile \ --pidfile "${PID_FILE}" \ --env HOME="${CHROOT_TOR_HOME_DIR}" \ -- "${CHROOT_EXEC}" \ --userspec="${TOR_USER}:${TOR_GROUP}" \ "${CHROOT_DIR}" \ /usr/bin/tor -f "${CHROOT_TOR_RC}" \ --DataDirectory "${CHROOT_TOR_DATA_DIR}" fi eend "$?" } _umount() { local dir="$1" _mountpoint "${dir}" || return 0 ebegin "Unmounting ${dir}" umount "${dir}" eend "$?" } _wait_no_use_dir() { local dir="$1" if fuser -s "${dir}" 2>/dev/null; then ebegin "Waiting until all processes stops using ${dir} (max. ${CHROOT_MOUNT_CHECK_TIMEOUT} seconds)" local waited=0 while sleep 1 waited=$(( waited + 1 )) fuser -s "${dir}" 2>/dev/null do if [ "${waited}" -eq "${CHROOT_MOUNT_CHECK_TIMEOUT}" ]; then eend 1 return 1 fi done eend 0 fi return 0 } stop() { ebegin "Stopping Tor (chroot mode)" _start_stop_daemon --stop --progress --signal INT --retry "${STOP_TIMEOUT}" \ --pidfile "${PID_FILE}" eend "$?" || return 1 _unmount_mounts } reload() { if [ ! -f "${PID_FILE}" ]; then eerror "${RC_SVCNAME} is not running" return 1 fi if [ "${CHROOT_TOR_RC_UPDATE}" = true ]; then _check_tor_rc "${TOR_RC}" "${CHROOT_DIR}${CHROOT_TOR_HOME_DIR}" \ "${CHROOT_DIR}${CHROOT_TOR_DATA_DIR}" true || return 1 else _check_tor_rc "${CHROOT_DIR}${CHROOT_TOR_RC}" "${CHROOT_DIR}${CHROOT_TOR_HOME_DIR}" \ "${CHROOT_DIR}${CHROOT_TOR_DATA_DIR}" true || return 1 fi if [ "${CHROOT_TOR_RC_UPDATE}" = true ]; then _update_torrc || return 1 fi ebegin "Reloading Tor configuration" _start_stop_daemon --signal HUP --pidfile "${PID_FILE}" eend "$?" }