mirror of https://github.com/digint/btrbk
Merge 57029783f9
into a75765cc9a
commit
07f76affe2
|
@ -1,3 +1,10 @@
|
||||||
|
btrbk-current
|
||||||
|
|
||||||
|
* ssh_filter_btrbk.sh uses new exit statuses on failures (1 when the
|
||||||
|
SSH command was rejected, 2 when the program’s arguments could not
|
||||||
|
be parsed).
|
||||||
|
* ssh_filter_btrbk.sh’s logging output format has changed slightly.
|
||||||
|
|
||||||
btrbk-0.32.5
|
btrbk-0.32.5
|
||||||
|
|
||||||
* Correct handling of zero-size raw info file (close #491).
|
* Correct handling of zero-size raw info file (close #491).
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
#!/bin/bash
|
#!/bin/sh
|
||||||
|
|
||||||
set -e
|
|
||||||
set -u
|
|
||||||
|
|
||||||
export PATH=/sbin:/bin:/usr/sbin:/usr/bin
|
# initialise and sanitise the shell execution environment
|
||||||
|
unset -v IFS
|
||||||
|
export LC_ALL=C
|
||||||
|
export PATH='/usr/bin:/bin'
|
||||||
|
|
||||||
|
set -e -u
|
||||||
|
|
||||||
enable_log=
|
enable_log=
|
||||||
restrict_path_list=
|
restrict_path_list=
|
||||||
|
@ -12,48 +15,86 @@ allow_exact_list=
|
||||||
allow_rate_limit=1
|
allow_rate_limit=1
|
||||||
allow_stream_buffer=1
|
allow_stream_buffer=1
|
||||||
allow_compress=1
|
allow_compress=1
|
||||||
compress_list="gzip|pigz|bzip2|pbzip2|bzip3|xz|lzop|lz4|zstd"
|
compress_list='gzip|pigz|bzip2|pbzip2|bzip3|xz|lzop|lz4|zstd'
|
||||||
|
|
||||||
# note that the backslash is NOT a metacharacter in a POSIX bracket expression!
|
# note that the backslash is NOT a metacharacter in a POSIX bracket expression!
|
||||||
option_match='-[a-zA-Z0-9=-]+' # matches short as well as long options
|
option_match='-[a-zA-Z0-9=-]+' # matches short as well as long options
|
||||||
file_match_sane='/[0-9a-zA-Z_@+./-]*' # matches file path (equal to $file_match in btrbk < 0.32.0)
|
file_match_sane='/[0-9a-zA-Z_@+./-]*' # matches file path (equal to ${file_match} in btrbk < 0.32.0)
|
||||||
file_match="/[^']*" # btrbk >= 0.32.0 quotes file arguments: match all but single quote
|
file_match="/[^']*" # btrbk >= 0.32.0 quotes file arguments: match all but single quote
|
||||||
file_arg_match="('${file_match}'|${file_match_sane})" # support btrbk < 0.32.0
|
file_arg_match="('${file_match}'|${file_match_sane})" # support btrbk < 0.32.0
|
||||||
|
|
||||||
|
is_pathname_absolute()
|
||||||
|
{
|
||||||
|
# Checks whether a string is an absolute pathname (that is: one that is non-
|
||||||
|
# empty and starts with either exactly one or more than two `/`).
|
||||||
|
|
||||||
|
local pathname="$1"
|
||||||
|
|
||||||
|
[ "${pathname}" != '//' ] || return 1
|
||||||
|
[ -n "${pathname##//[!/]*}" ] || return 1
|
||||||
|
[ -z "${pathname##/*}" ] || return 1
|
||||||
|
[ -n "${pathname}" ] || return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
print_normalised_pathname()
|
||||||
|
{
|
||||||
|
# Normalises a pathname given via the positional parameter #1 as follows:
|
||||||
|
# * Folds any >=3 leading `/` into 1.
|
||||||
|
# POSIX specifies that implementations may treat exactly 2 leading `//`
|
||||||
|
# specially and therefore such are not folded here.
|
||||||
|
# * Folds any >=2 non-leading `/` into 1.
|
||||||
|
# * Strips any trailing `/`.
|
||||||
|
|
||||||
|
local pathname="$1"
|
||||||
|
|
||||||
|
printf '%s' "${pathname}" | sed -E 's%^///+%/%; s%(.)//+%\1/%g; s%/+$%%'
|
||||||
|
}
|
||||||
|
|
||||||
log_cmd()
|
log_cmd()
|
||||||
{
|
{
|
||||||
if [[ -n "$enable_log" ]]; then
|
local priority="$1"
|
||||||
logger -p $1 -t ssh_filter_btrbk.sh "$2 (Name: ${LOGNAME:-<unknown>}; Remote: ${SSH_CLIENT:-<unknown>})${3:+: $3}: $SSH_ORIGINAL_COMMAND"
|
local authorisation_decision="$2"
|
||||||
|
local reason="${3-}"
|
||||||
|
|
||||||
|
if [ -n "${enable_log}" ]; then
|
||||||
|
logger -p "${priority}" -t ssh_filter_btrbk.sh "${authorisation_decision} (Name: ${LOGNAME:-<unknown>}; Connection: ${SSH_CONNECTION:-<unknown>})${reason:+: ${reason}}: ${SSH_ORIGINAL_COMMAND}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
allow_cmd()
|
allow_cmd()
|
||||||
{
|
{
|
||||||
allow_list="${allow_list}|$1"
|
local cmd="$1"
|
||||||
|
|
||||||
|
allow_list="${allow_list}|${cmd}"
|
||||||
}
|
}
|
||||||
|
|
||||||
allow_exact_cmd()
|
allow_exact_cmd()
|
||||||
{
|
{
|
||||||
allow_exact_list="${allow_exact_list}|$1"
|
local cmd="$1"
|
||||||
|
|
||||||
|
allow_exact_list="${allow_exact_list}|${cmd}"
|
||||||
}
|
}
|
||||||
|
|
||||||
reject_and_die()
|
reject_and_die()
|
||||||
{
|
{
|
||||||
local reason=$1
|
local reason="$1"
|
||||||
log_cmd "auth.err" "btrbk REJECT" "$reason"
|
|
||||||
echo "ERROR: ssh_filter_btrbk.sh: ssh command rejected: $reason: $SSH_ORIGINAL_COMMAND" 1>&2
|
log_cmd 'auth.err' 'btrbk REJECT' "${reason}"
|
||||||
exit 255
|
printf 'ERROR: ssh_filter_btrbk.sh: ssh command rejected: %s: %s\n' "${reason}" "${SSH_ORIGINAL_COMMAND}" >&2
|
||||||
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
run_cmd()
|
run_cmd()
|
||||||
{
|
{
|
||||||
log_cmd "auth.info" "btrbk ACCEPT"
|
log_cmd 'auth.info' 'btrbk ACCEPT'
|
||||||
eval " $SSH_ORIGINAL_COMMAND"
|
eval " ${SSH_ORIGINAL_COMMAND}"
|
||||||
}
|
}
|
||||||
|
|
||||||
reject_filtered_cmd()
|
reject_filtered_cmd()
|
||||||
{
|
{
|
||||||
if [[ -n "$restrict_path_list" ]]; then
|
if [ -n "${restrict_path_list}" ]; then
|
||||||
# match any of restrict_path_list,
|
# match any of restrict_path_list,
|
||||||
# or any file/directory (matching file_match) below restrict_path
|
# or any file/directory (matching file_match) below restrict_path
|
||||||
path_match="'(${restrict_path_list})(${file_match})?'"
|
path_match="'(${restrict_path_list})(${file_match})?'"
|
||||||
|
@ -66,7 +107,7 @@ reject_filtered_cmd()
|
||||||
# btrbk >= 0.32.0 quotes files, allow both (legacy)
|
# btrbk >= 0.32.0 quotes files, allow both (legacy)
|
||||||
path_match="(${path_match}|${path_match_legacy})"
|
path_match="(${path_match}|${path_match_legacy})"
|
||||||
|
|
||||||
if [[ -n "$allow_compress" ]]; then
|
if [ -n "${allow_compress}" ]; then
|
||||||
decompress_match="(${compress_list}) -d -c( -[pT][0-9]+)?"
|
decompress_match="(${compress_list}) -d -c( -[pT][0-9]+)?"
|
||||||
compress_match="(${compress_list}) -c( -[0-9])?( -[pT][0-9]+)?"
|
compress_match="(${compress_list}) -c( -[0-9])?( -[pT][0-9]+)?"
|
||||||
else
|
else
|
||||||
|
@ -76,8 +117,8 @@ reject_filtered_cmd()
|
||||||
|
|
||||||
# rate_limit_remote and stream_buffer_remote use combined
|
# rate_limit_remote and stream_buffer_remote use combined
|
||||||
# "mbuffer" as of btrbk-0.29.0
|
# "mbuffer" as of btrbk-0.29.0
|
||||||
if [[ -n "$allow_stream_buffer" ]] || [[ -n "$allow_rate_limit" ]]; then
|
if [ -n "${allow_stream_buffer}" ] || [ -n "${allow_rate_limit}" ]; then
|
||||||
mbuffer_match="mbuffer -v 1 -q( -s [0-9]+[kmgKMG]?)?( -m [0-9]+[kmgKMG]?)?( -[rR] [0-9]+[kmgtKMGT]?)?"
|
mbuffer_match='mbuffer -v 1 -q( -s [0-9]+[kmgKMG]?)?( -m [0-9]+[kmgKMG]?)?( -[rR] [0-9]+[kmgtKMGT]?)?'
|
||||||
else
|
else
|
||||||
mbuffer_match=
|
mbuffer_match=
|
||||||
fi
|
fi
|
||||||
|
@ -87,31 +128,39 @@ reject_filtered_cmd()
|
||||||
stream_in_match="(${decompress_match} \| )?(${mbuffer_match} \| )?"
|
stream_in_match="(${decompress_match} \| )?(${mbuffer_match} \| )?"
|
||||||
stream_out_match="( \| ${mbuffer_match})?( \| ${compress_match}$)?"
|
stream_out_match="( \| ${mbuffer_match})?( \| ${compress_match}$)?"
|
||||||
|
|
||||||
|
# `grep`’s `-q`-option is not used as it may cause an exit status of `0` even
|
||||||
|
# when an error occurred.
|
||||||
|
|
||||||
allow_stream_match="^${stream_in_match}${allow_cmd_match}${stream_out_match}"
|
allow_stream_match="^${stream_in_match}${allow_cmd_match}${stream_out_match}"
|
||||||
if [[ $SSH_ORIGINAL_COMMAND =~ $allow_stream_match ]] ; then
|
if printf '%s' "${SSH_ORIGINAL_COMMAND}" | grep -E "${allow_stream_match}" >/dev/null 2>/dev/null; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exact_cmd_match="^(${allow_exact_list})$";
|
exact_cmd_match="^(${allow_exact_list})$";
|
||||||
if [[ $SSH_ORIGINAL_COMMAND =~ $exact_cmd_match ]] ; then
|
if printf '%s' "${SSH_ORIGINAL_COMMAND}" | grep -E "${exact_cmd_match}" >/dev/null 2>/dev/null; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
reject_and_die "disallowed command${restrict_path_list:+ (restrict-path: \"${restrict_path_list//|/\", \"}\")}"
|
local formatted_restrict_path_list="$(printf '%s' "${restrict_path_list}" | sed 's/|/", "/g')"
|
||||||
|
reject_and_die "disallowed command${restrict_path_list:+ (restrict-path: \"${formatted_restrict_path_list}\")}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# check for "--sudo" option before processing other options
|
# check for "--sudo" option before processing other options
|
||||||
sudo_prefix=
|
sudo_prefix=
|
||||||
for key; do
|
for key in "$@"; do
|
||||||
[[ "$key" == "--sudo" ]] && sudo_prefix="sudo -n "
|
if [ "${key}" = '--sudo' ]; then
|
||||||
[[ "$key" == "--doas" ]] && sudo_prefix="doas -n "
|
sudo_prefix='sudo -n '
|
||||||
|
fi
|
||||||
|
if [ "${key}" = '--doas' ]; then
|
||||||
|
sudo_prefix='doas -n '
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
while [[ "$#" -ge 1 ]]; do
|
while [ "$#" -ge 1 ]; do
|
||||||
key="$1"
|
key="$1"
|
||||||
|
|
||||||
case $key in
|
case "${key}" in
|
||||||
-l|--log)
|
-l|--log)
|
||||||
enable_log=1
|
enable_log=1
|
||||||
;;
|
;;
|
||||||
|
@ -121,7 +170,12 @@ while [[ "$#" -ge 1 ]]; do
|
||||||
;;
|
;;
|
||||||
|
|
||||||
-p|--restrict-path)
|
-p|--restrict-path)
|
||||||
restrict_path_list="${restrict_path_list}|${2%/}" # add to list while removing trailing slash
|
# check whether the pathname is absolute
|
||||||
|
if ! is_pathname_absolute "$2"; then
|
||||||
|
reject_and_die "pathname \"$2\" given to the \"--restrict-path\"-option is not absolute"
|
||||||
|
fi
|
||||||
|
|
||||||
|
restrict_path_list="${restrict_path_list}|$(print_normalised_pathname "$2")"
|
||||||
shift # past argument
|
shift # past argument
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
@ -161,8 +215,8 @@ while [[ "$#" -ge 1 ]]; do
|
||||||
;;
|
;;
|
||||||
|
|
||||||
*)
|
*)
|
||||||
echo "ERROR: ssh_filter_btrbk.sh: failed to parse command line option: $key" 1>&2
|
printf 'ERROR: ssh_filter_btrbk.sh: failed to parse command line option: %s\n' "${key}" >&2
|
||||||
exit 255
|
exit 2
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
|
@ -173,15 +227,15 @@ done
|
||||||
allow_exact_cmd "${sudo_prefix}btrfs subvolume (show|list)( ${option_match})* ${file_arg_match}";
|
allow_exact_cmd "${sudo_prefix}btrfs subvolume (show|list)( ${option_match})* ${file_arg_match}";
|
||||||
allow_cmd "${sudo_prefix}readlink" # resolve symlink
|
allow_cmd "${sudo_prefix}readlink" # resolve symlink
|
||||||
allow_exact_cmd "${sudo_prefix}test -d ${file_arg_match}" # check directory (only for compat=busybox)
|
allow_exact_cmd "${sudo_prefix}test -d ${file_arg_match}" # check directory (only for compat=busybox)
|
||||||
allow_exact_cmd "cat /proc/self/mountinfo" # resolve mountpoints
|
allow_exact_cmd 'cat /proc/self/mountinfo' # resolve mountpoints
|
||||||
allow_exact_cmd "cat /proc/self/mounts" # legacy, for btrbk < 0.27.0
|
allow_exact_cmd 'cat /proc/self/mounts' # legacy, for btrbk < 0.27.0
|
||||||
|
|
||||||
# remove leading "|" on alternation lists
|
# remove leading "|" on alternation lists
|
||||||
allow_list=${allow_list#\|}
|
allow_list="${allow_list#\|}"
|
||||||
allow_exact_list=${allow_exact_list#\|}
|
allow_exact_list="${allow_exact_list#\|}"
|
||||||
restrict_path_list=${restrict_path_list#\|}
|
restrict_path_list="${restrict_path_list#\|}"
|
||||||
|
|
||||||
case "$SSH_ORIGINAL_COMMAND" in
|
case "${SSH_ORIGINAL_COMMAND}" in
|
||||||
*\.\./*) reject_and_die 'directory traversal' ;;
|
*\.\./*) reject_and_die 'directory traversal' ;;
|
||||||
*'
|
*'
|
||||||
'*) reject_and_die 'unsafe character LF' ;;
|
'*) reject_and_die 'unsafe character LF' ;;
|
||||||
|
@ -193,7 +247,7 @@ case "$SSH_ORIGINAL_COMMAND" in
|
||||||
*\<*) reject_and_die 'unsafe character "<"' ;;
|
*\<*) reject_and_die 'unsafe character "<"' ;;
|
||||||
*\>*) reject_and_die 'unsafe character ">"' ;;
|
*\>*) reject_and_die 'unsafe character ">"' ;;
|
||||||
*\`*) reject_and_die 'unsafe character "`"' ;;
|
*\`*) reject_and_die 'unsafe character "`"' ;;
|
||||||
*\|*) [[ -n "$allow_compress" ]] || [[ -n "$allow_rate_limit" ]] || [[ -n "$allow_stream_buffer" ]] || reject_and_die 'unsafe character "|"' ;;
|
*\|*) [ -n "${allow_compress}" ] || [ -n "${allow_rate_limit}" ] || [ -n "${allow_stream_buffer}" ] || reject_and_die 'unsafe character "|"' ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
reject_filtered_cmd
|
reject_filtered_cmd
|
||||||
|
|
Loading…
Reference in New Issue