#!/bin/sh # # Config for bkctld # # shellcheck disable=SC2034 [ -f /etc/default/bkctld ] && . /etc/default/bkctld VERSION="2.7.0" LIBDIR=${LIBDIR:-/usr/lib/bkctld} CONFDIR="${CONFDIR:-/etc/evobackup}" BACKUP_DISK="${BACKUP_DISK:-}" BACKUP_PARTITION="${BACKUP_PARTITION:-/backup}" JAILDIR="${JAILDIR:-${BACKUP_PARTITION}/jails}" INCDIR="${INCDIR:-${BACKUP_PARTITION}/incs}" TPLDIR="${TPLDIR:-/usr/share/bkctld}" LOCALTPLDIR="${LOCALTPLDIR:-/usr/local/share/bkctld}" LOCKDIR="${LOCKDIR:-/run/lock/bkctld}" INDEX_DIR="${INDEX_DIR:-${BACKUP_PARTITION}/index}" IDX_FILE="${IDX_FILE:-${INDEX_DIR}/bkctld-jails.idx}" SSHD_PID="${SSHD_PID:-/run/sshd.pid}" SSHD_CONFIG="${SSHD_CONFIG:-/etc/ssh/sshd_config}" AUTHORIZED_KEYS="${AUTHORIZED_KEYS:-/root/.ssh/authorized_keys}" FIREWALL_RULES="${FIREWALL_RULES:-}" LOGLEVEL="${LOGLEVEL:-6}" CRITICAL="${CRITICAL:-48}" WARNING="${WARNING:-24}" DUC=$(command -v duc-nox || command -v duc) FORCE="${FORCE:-0}" show_version() { cat <, Victor Laborie , Jérémy Lecour and others. bkctld comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. See the GNU Affero General Public License v3.0 for details. END } show_help() { cat < [arguments] Options -h|--help|-? Display help -V|--version Display version, authors and license Subcommands: EOF for filename in ${LIBDIR}/bkctld-*; do desc=$(grep -E "^# Description:" "${filename}"|sed "s/^# Description: //") usage=$(grep -E "^# Usage:" "${filename}"|sed "s/^# Usage: //") printf " %- 32s %s\n" "${usage}" "${desc}" done printf "\n" } log_date() { echo "[$(date +"%Y-%m-%d %H:%M:%S")]" } process_name() { basename $0 } debug() { msg="${1:-$(cat /dev/stdin)}" if [ "${LOGLEVEL}" -ge 7 ]; then echo "$(log_date) DEBUG $(process_name) ${msg}" logger -t bkctld -p daemon.debug "$(process_name) ${msg}" fi } info() { msg="${1:-$(cat /dev/stdin)}" if [ "${LOGLEVEL}" -ge 6 ]; then tty -s && echo "$(log_date) INFO $(process_name) ${msg}" logger -t bkctld -p daemon.info "$(process_name) ${msg}" fi } notice() { msg="${1:-$(cat /dev/stdin)}" tty -s && echo "$(log_date) NOTICE $(process_name) ${msg}" [ "${LOGLEVEL}" -ge 5 ] && logger -t bkctld -p daemon.notice "$(process_name) ${msg}" } warning() { msg="${1:-$(cat /dev/stdin)}" tty -s && echo "$(log_date) WARNING $(process_name) ${msg}" >&2 if [ "${LOGLEVEL}" -ge 4 ]; then tty -s || echo "$(log_date) WARNING $(process_name) ${msg}" >&2 logger -t bkctld -p daemon.warning "$(process_name) ${msg}" fi } # Return codes # 1 : generic error # 2 : jail not found # > 100 : subcommands specific errors error() { msg="${1:-$(cat /dev/stdin)}" rc="${2:-1}" tty -s && echo "$(log_date) ERROR $(process_name) ${msg}" >&2 if [ "${LOGLEVEL}" -ge 5 ]; then tty -s || echo "$(log_date) ERROR $(process_name) ${msg}" >&2 logger -t bkctld -p daemon.error "$(process_name) ${msg}" fi exit ${rc} } dry_run() { test "$DRY_RUN" = "1" } current_time() { date +"%H:%M:%S" } # Returns true if the given path is on a btrfs filesystem is_btrfs() { path=$1 inode=$(stat --format=%i "${path}") test $inode -eq 256 } # Returns the list of jails found in the "jails" directory (default) jails_list() { # TODO: try if this command works the same : # find "${JAILDIR}" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' find "${JAILDIR}" -mindepth 1 -maxdepth 1 -type d | sed 's!.*/!!' | sort -h } # Returns the list of jails found in the "incs" directory jails_with_incs_list() { find "${INCDIR}" -mindepth 1 -maxdepth 1 -type d | sed 's!.*/!!' | sort -h } # Returns the complete path of a jail jail_path() { jail_name=${1:?} echo "${JAILDIR}/${jail_name}" } jail_config_dir() { jail_name=${1:?} echo "${CONFDIR}/${jail_name}.d" } jail_incs_policy_file() { jail_name=${1:?} jail_config_dir=$(jail_config_dir "${jail_name}") echo "${jail_config_dir}/incs_policy" } # Returns the path of incs for a jail incs_path() { jail_name=${1:?} echo "${INCDIR}/${jail_name}" } # Returns the path of a specific inc for a jail inc_path() { jail_name=${1:?} inc_name=${2:?} echo "${INCDIR}/${jail_name}/${inc_name}" } # Test the existence of an inc pattern for a jail inc_exists() { jail_name=${1-?} inc_pattern=${2-?} inc_path=$(inc_path "${jail_name}" "${inc_pattern}") # inc_path must not be quoted because it can contain globs ls -d ${inc_path} > /dev/null 2>&1 } # Returns the list of all incs for a jail incs_list() { jail_name=${1:?} find "$(incs_path "${jail_name}")" -mindepth 1 -maxdepth 1 -type d | sed 's!.*/!!' | sort -h } # Return the list of empty incs directories empty_incs_list() { find ${INCDIR} -mindepth 1 -maxdepth 1 -type d -empty } current_jail_incs_policy_file() { jail_name=${1:?} new_file="$(jail_incs_policy_file "${jail_name}")" old_file="${CONFDIR}/${jail_name}" if [ -f "${new_file}" ]; then echo "${new_file}" elif [ -f "${old_file}" ]; then echo "${old_file}" else echo "" fi } jail_check_policy_file() { jail_name=${1:?} jail_config_dir=$(jail_config_dir "${jail_name}") echo "${jail_config_dir}/check_policy" } current_jail_check_policy_file() { jail_name=${1:?} new_file="$(jail_check_policy_file "${jail_name}")" # old_file="${JAILDIR}/${jail_name}/etc/bkctld-check" if [ -f "${new_file}" ]; then echo "${new_file}" # elif [ -f "${old_file}" ]; then # echo "${old_file}" else echo "" fi } # relative_date "+%Y-%m-%d.-2day" relative_date() { format=$(echo $1 | cut -d'.' -f1) time_jump=$(echo $1 | cut -d'.' -f2) reference_date=$(date "${format}") past_date=$(date --date "${reference_date} ${time_jump}" +"%Y-%m-%d") echo ${past_date} } new_tmp_file() { name=${1:-} mktemp --tmpdir=/tmp "bkctld.${$}.${name}.XXXXX" } new_tmp_dir() { name=${1:-} mktemp --directory --tmpdir=/tmp "bkctld.${$}.${name}.XXXXX" } cleanup_tmp() { find /tmp -name "bkctld.${$}.*" -delete } new_lock_file() { lock_file=${1:-} lock_dir=$(dirname "${lock_file}") mkdir --parents "${lock_dir}" && echo $$ > ${lock_file} || error "Failed to acquire lock file '${lock_file}'" } pkg_version() { # $(command -v ssh) -V 2>&1 | grep -iEo 'OpenSSH_(\S+)' | cut -d '_' -f2 dpkg-query -W -f='${Version}\n' $1 \ | sed 's/[~+-].\+//' \ | sed 's/.\+://' \ | sed 's/p.*//' \ | cut -d. -f1,2 } ssh_keygen_with_prefix() { # openssh-client 7.9 provides ssh-keygen with "-f prefix_path" option dpkg --compare-versions "$(pkg_version 'openssh-client')" ge "7.9" } setup_jail_chroot() { jail_name=${1:?} jail_path=$(jail_path "${jail_name}") passwd="${TPLDIR}/passwd" shadow="${TPLDIR}/shadow" group="${TPLDIR}/group" sshrc="${TPLDIR}/sshrc" [ -f "${LOCALTPLDIR}/passwd" ] && passwd="${LOCALTPLDIR}/passwd" [ -f "${LOCALTPLDIR}/shadow" ] && shadow="${LOCALTPLDIR}/shadow" [ -f "${LOCALTPLDIR}/group" ] && group="${LOCALTPLDIR}/group" [ -f "${LOCALTPLDIR}/sshrc" ] && group="${LOCALTPLDIR}/sshrc" cd "${jail_path}" || error "${jail_name}: failed to change directory to ${jail_path}." umask 077 info "1 - Creating the chroot" rm -rf ./bin rm -rf ./lib rm -rf ./lib64 rm -rf ./run rm -rf ./usr rm -rf ./var/run # Let's not delete the existing SSH host keys, # otherwise the clients will have to accept the new keys mkdir -p ./dev mkdir -p ./proc mkdir -p ./usr/bin mkdir -p ./usr/sbin mkdir -p ./usr/lib mkdir -p ./usr/lib/x86_64-linux-gnu mkdir -p ./usr/lib/openssh mkdir -p ./usr/lib64 mkdir -p ./etc/ssh mkdir -p ./var/log mkdir -p ./run/sshd # shellcheck disable=SC2174 mkdir -p ./root/.ssh --mode 0700 # shellcheck disable=SC2174 mkdir -p ./var/backup --mode 0700 ln -s ./usr/bin ./bin ln -s ./usr/lib ./lib ln -s ./usr/lib64 ./lib64 ln -s --target-directory=./var ../run touch ./var/log/lastlog ./var/log/wtmp ./run/utmp info "2 - Copying essential files" # if ssh_keygen_with_prefix; then # Generate SSH host keys if missing in jail ssh-keygen -A -f "${jail_path}" else # Copy SSH host keys from host if missing in jail for key in /etc/ssh/*_key; do cp --no-clobber ${key} ${jail_path}${key}; done fi touch "./${AUTHORIZED_KEYS}" chmod 600 "./${AUTHORIZED_KEYS}" cp "${passwd}" ./etc cp "${shadow}" ./etc cp "${group}" ./etc cp "${sshrc}" ./etc/ssh info "3 - Copying binaries" cp -f /lib/ld-linux.so.2 ./lib 2>/dev/null || cp -f /lib64/ld-linux-x86-64.so.2 ./lib64 cp /lib/x86_64-linux-gnu/libnss* ./lib/x86_64-linux-gnu for dbin in \ /bin/sh \ /bin/ls \ /bin/mkdir \ /bin/cat \ /bin/rm \ /bin/sed \ /usr/bin/rsync \ /usr/bin/lastlog \ /usr/bin/touch \ /usr/sbin/sshd \ /usr/lib/openssh/sftp-server\ ; do cp -f "${dbin}" "./${dbin}"; for lib in $(ldd "${dbin}" | grep -Eo "/.*so.[0-9\.]+"); do cp -p "${lib}" "./${lib}" done done } setup_jail_config() { jail_name=${1:?} jail_path=$(jail_path "${jail_name}") jail_sshd_config="${jail_path}/${SSHD_CONFIG}" sshd_config_tpl="${TPLDIR}/sshd_config" test -f "${LOCALTPLDIR}/sshd_config" && sshd_config_tpl="${LOCALTPLDIR}/sshd_config" info "4 - Copie default sshd_config" install -m 0640 "${sshd_config_tpl}" "${jail_sshd_config}" info "5 - Copie default inc configuration" incs_policy_tpl="${TPLDIR}/incs_policy.tpl" test -f "${LOCALTPLDIR}/incs_policy.tpl" && incs_policy_tpl="${LOCALTPLDIR}/incs_policy.tpl" jail_incs_policy_file=$(jail_incs_policy_file "${jail_name}") mkdir --parents "$(dirname "${jail_incs_policy_file}")" install -m 0640 "${incs_policy_tpl}" "${jail_incs_policy_file}" check_policy_tpl="${TPLDIR}/check_policy.tpl" test -f "${LOCALTPLDIR}/check_policy.tpl" && check_policy_tpl="${LOCALTPLDIR}/check_policy.tpl" jail_check_policy_file=$(jail_check_policy_file "${jail_name}") mkdir --parents "$(dirname "${jail_check_policy_file}")" install -m 0640 "${check_policy_tpl}" "${jail_check_policy_file}" "${LIBDIR}/bkctld-port" "${jail_name}" auto } is_mounted_inside_jail() { target=${1:?} # TODO: try to find why it doesn't work with this findmnt(8) command # findmnt --target "${target}" --tab-file /proc/mounts grep -q "${target}" /proc/mounts } mount_jail_fs() { jail_name=${1:?} jail_path=$(jail_path "${jail_name}") is_mounted_inside_jail "${jail_path}/dev" || mount -nt tmpfs "dev-${jail_name}" "${jail_path}/dev" [ -e "dev/console" ] || mknod -m 622 "${jail_path}/dev/console" c 5 1 chown root:tty "${jail_path}/dev/console" [ -e "dev/null" ] || mknod -m 666 "${jail_path}/dev/null" c 1 3 [ -e "dev/zero" ] || mknod -m 666 "${jail_path}/dev/zero" c 1 5 [ -e "dev/ptmx" ] || mknod -m 666 "${jail_path}/dev/ptmx" c 5 2 chown root:tty "${jail_path}/dev/ptmx" [ -e "dev/tty" ] || mknod -m 666 "${jail_path}/dev/tty" c 5 0 chown root:tty "${jail_path}/dev/tty" [ -e "dev/random" ] || mknod -m 444 "${jail_path}/dev/random" c 1 8 [ -e "dev/urandom" ] || mknod -m 444 "${jail_path}/dev/urandom" c 1 9 mkdir -p "${jail_path}/dev/pts" is_mounted_inside_jail "${jail_path}/dev/pts" || mount -t devpts -o gid=4,mode=620 none "${jail_path}/dev/pts" mkdir -p "${jail_path}/dev/shm" is_mounted_inside_jail "${jail_path}/dev/shm" || mount -t tmpfs none "${jail_path}/dev/shm" is_mounted_inside_jail "${jail_path}/proc" || mount -t proc "proc-${jail_name}" "${jail_path}/proc" ln -fs "${jail_path}/proc/self/fd" "${jail_path}/dev/fd" ln -fs "${jail_path}/proc/self/fd/0" "${jail_path}/dev/stdin" ln -fs "${jail_path}/proc/self/fd/1" "${jail_path}/dev/stdout" ln -fs "${jail_path}/proc/self/fd/2" "${jail_path}/dev/stderr" ln -fs "${jail_path}/proc/kcore" "${jail_path}/dev/core" } read_variable() { file=${1:?} var_name=${2:?} pattern="^\s*${var_name}=.+" grep --extended-regexp --only-matching "${pattern}" "${file}" | cut -d= -f2 } read_numerical_variable() { file=${1:?} var_name=${2:?} pattern="^\s*${var_name}=-?[0-9]+" grep --extended-regexp --only-matching "${pattern}" "${file}" | cut -d= -f2 }