2015-09-02 19:20:58 +02:00
|
|
|
#!/bin/bash
|
2015-02-09 11:42:44 +01:00
|
|
|
|
2015-07-08 14:54:56 +02:00
|
|
|
set -e
|
|
|
|
set -u
|
|
|
|
|
2015-05-18 21:18:57 +02:00
|
|
|
export PATH=/sbin:/bin:/usr/sbin:/usr/bin
|
2015-02-09 11:42:44 +01:00
|
|
|
|
2015-07-08 14:54:56 +02:00
|
|
|
enable_log=
|
2015-09-02 19:20:58 +02:00
|
|
|
restrict_path_list=
|
|
|
|
allow_list=
|
2016-03-31 14:30:53 +02:00
|
|
|
allow_exact_list=
|
2017-03-17 20:39:51 +01:00
|
|
|
allow_rate_limit=1
|
2017-08-21 14:39:40 +02:00
|
|
|
allow_stream_buffer=1
|
2017-03-17 21:35:16 +01:00
|
|
|
allow_compress=1
|
2020-12-23 23:11:41 +01:00
|
|
|
compress_list="gzip|pigz|bzip2|pbzip2|xz|lzop|lz4|zstd"
|
2015-02-09 11:42:44 +01:00
|
|
|
|
2018-10-10 22:43:28 +02:00
|
|
|
# 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
|
2021-07-14 20:34:40 +02:00
|
|
|
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_arg_match="('${file_match}'|${file_match_sane})" # support btrbk < 0.32.0
|
2018-10-10 22:43:28 +02:00
|
|
|
|
2015-07-08 18:05:39 +02:00
|
|
|
log_cmd()
|
2015-02-09 11:42:44 +01:00
|
|
|
{
|
2015-09-02 19:20:58 +02:00
|
|
|
if [[ -n "$enable_log" ]]; then
|
|
|
|
logger -p $1 -t ssh_filter_btrbk.sh "$2 (Name: ${LOGNAME:-<unknown>}; Remote: ${SSH_CLIENT:-<unknown>})${3:+: $3}: $SSH_ORIGINAL_COMMAND"
|
2015-02-09 11:42:44 +01:00
|
|
|
fi
|
2015-07-08 18:05:39 +02:00
|
|
|
}
|
|
|
|
|
2015-09-02 19:20:58 +02:00
|
|
|
allow_cmd()
|
|
|
|
{
|
|
|
|
allow_list="${allow_list}|$1"
|
|
|
|
}
|
|
|
|
|
2016-03-31 14:30:53 +02:00
|
|
|
allow_exact_cmd()
|
|
|
|
{
|
|
|
|
allow_exact_list="${allow_exact_list}|$1"
|
|
|
|
}
|
|
|
|
|
2015-07-08 18:05:39 +02:00
|
|
|
reject_and_die()
|
|
|
|
{
|
2015-09-02 19:20:58 +02:00
|
|
|
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
|
2019-08-18 13:49:37 +02:00
|
|
|
exit 255
|
2015-02-09 11:42:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
run_cmd()
|
|
|
|
{
|
2015-07-08 18:05:39 +02:00
|
|
|
log_cmd "auth.info" "btrbk ACCEPT"
|
2016-11-21 14:15:57 +01:00
|
|
|
eval " $SSH_ORIGINAL_COMMAND"
|
2015-09-02 19:20:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
reject_filtered_cmd()
|
|
|
|
{
|
|
|
|
if [[ -n "$restrict_path_list" ]]; then
|
2021-07-14 20:34:40 +02:00
|
|
|
# match any of restrict_path_list,
|
2015-09-02 19:20:58 +02:00
|
|
|
# or any file/directory (matching file_match) below restrict_path
|
2021-07-14 20:34:40 +02:00
|
|
|
path_match="'(${restrict_path_list})(${file_match})?'"
|
|
|
|
path_match_legacy="(${restrict_path_list})(${file_match_sane})?"
|
2015-09-02 19:20:58 +02:00
|
|
|
else
|
|
|
|
# match any absolute file/directory (matching file_match)
|
2021-07-14 20:34:40 +02:00
|
|
|
path_match="'${file_match}'"
|
|
|
|
path_match_legacy="${file_match_sane}"
|
2015-09-02 19:20:58 +02:00
|
|
|
fi
|
2021-07-14 20:34:40 +02:00
|
|
|
# btrbk >= 0.32.0 quotes files, allow both (legacy)
|
|
|
|
path_match="(${path_match}|${path_match_legacy})"
|
2015-09-02 19:20:58 +02:00
|
|
|
|
2016-08-18 17:41:26 +02:00
|
|
|
if [[ -n "$allow_compress" ]]; then
|
2019-07-30 21:50:52 +02:00
|
|
|
decompress_match="(${compress_list}) -d -c( -[pT][0-9]+)?"
|
|
|
|
compress_match="(${compress_list}) -c( -[0-9])?( -[pT][0-9]+)?"
|
2016-08-18 17:41:26 +02:00
|
|
|
else
|
|
|
|
decompress_match=
|
|
|
|
compress_match=
|
|
|
|
fi
|
|
|
|
|
2019-07-30 21:50:52 +02:00
|
|
|
# rate_limit_remote and stream_buffer_remote use combined
|
|
|
|
# "mbuffer" as of btrbk-0.29.0
|
|
|
|
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]?)?"
|
2017-08-21 14:39:40 +02:00
|
|
|
else
|
2019-07-30 21:50:52 +02:00
|
|
|
mbuffer_match=
|
2017-03-17 20:39:51 +01:00
|
|
|
fi
|
|
|
|
|
2015-09-02 19:20:58 +02:00
|
|
|
# allow multiple paths (e.g. "btrfs subvolume snapshot <src> <dst>")
|
2019-07-30 21:50:52 +02:00
|
|
|
allow_cmd_match="(${allow_list})( ${option_match})*( ${path_match})+"
|
|
|
|
stream_in_match="(${decompress_match} \| )?(${mbuffer_match} \| )?"
|
|
|
|
stream_out_match="( \| ${mbuffer_match})?( \| ${compress_match}$)?"
|
2015-09-02 19:20:58 +02:00
|
|
|
|
2019-07-30 21:50:52 +02:00
|
|
|
allow_stream_match="^${stream_in_match}${allow_cmd_match}${stream_out_match}"
|
|
|
|
if [[ $SSH_ORIGINAL_COMMAND =~ $allow_stream_match ]] ; then
|
2016-03-31 14:30:53 +02:00
|
|
|
return 0
|
2015-09-02 19:20:58 +02:00
|
|
|
fi
|
2016-03-31 14:30:53 +02:00
|
|
|
|
2021-03-21 12:53:22 +01:00
|
|
|
exact_cmd_match="^(${allow_exact_list})$";
|
2016-03-31 14:30:53 +02:00
|
|
|
if [[ $SSH_ORIGINAL_COMMAND =~ $exact_cmd_match ]] ; then
|
|
|
|
return 0
|
|
|
|
fi
|
|
|
|
|
|
|
|
reject_and_die "disallowed command${restrict_path_list:+ (restrict-path: \"${restrict_path_list//|/\", \"}\")}"
|
2015-02-09 11:42:44 +01:00
|
|
|
}
|
|
|
|
|
2015-09-02 19:20:58 +02:00
|
|
|
|
2017-03-17 21:22:13 +01:00
|
|
|
# check for "--sudo" option before processing other options
|
2016-11-21 14:15:57 +01:00
|
|
|
sudo_prefix=
|
2017-03-17 21:22:13 +01:00
|
|
|
for key; do
|
|
|
|
[[ "$key" == "--sudo" ]] && sudo_prefix="sudo -n "
|
2022-02-08 01:03:32 +01:00
|
|
|
[[ "$key" == "--doas" ]] && sudo_prefix="doas -n "
|
2017-03-17 21:22:13 +01:00
|
|
|
done
|
|
|
|
|
2015-09-02 19:20:58 +02:00
|
|
|
while [[ "$#" -ge 1 ]]; do
|
|
|
|
key="$1"
|
|
|
|
|
|
|
|
case $key in
|
|
|
|
-l|--log)
|
2016-03-31 15:05:08 +02:00
|
|
|
enable_log=1
|
|
|
|
;;
|
2015-09-02 19:20:58 +02:00
|
|
|
|
2022-02-08 01:03:32 +01:00
|
|
|
--sudo|--doas)
|
2017-03-17 21:22:13 +01:00
|
|
|
# already processed above
|
2016-03-31 15:05:08 +02:00
|
|
|
;;
|
2015-09-02 19:20:58 +02:00
|
|
|
|
|
|
|
-p|--restrict-path)
|
2016-03-31 15:05:08 +02:00
|
|
|
restrict_path_list="${restrict_path_list}|${2%/}" # add to list while removing trailing slash
|
|
|
|
shift # past argument
|
|
|
|
;;
|
2015-09-02 19:20:58 +02:00
|
|
|
|
|
|
|
-s|--source)
|
2016-11-21 14:15:57 +01:00
|
|
|
allow_cmd "${sudo_prefix}btrfs subvolume snapshot"
|
|
|
|
allow_cmd "${sudo_prefix}btrfs send"
|
2016-03-31 15:05:08 +02:00
|
|
|
;;
|
2015-09-02 19:20:58 +02:00
|
|
|
|
|
|
|
-t|--target)
|
2016-11-21 14:15:57 +01:00
|
|
|
allow_cmd "${sudo_prefix}btrfs receive"
|
2019-09-26 19:50:23 +02:00
|
|
|
allow_cmd "${sudo_prefix}mkdir"
|
2016-03-31 15:05:08 +02:00
|
|
|
;;
|
2015-09-02 19:20:58 +02:00
|
|
|
|
2016-08-18 17:41:26 +02:00
|
|
|
-c|--compress)
|
2017-03-17 21:35:16 +01:00
|
|
|
# deprecated option, compression is always allowed
|
2016-08-18 17:41:26 +02:00
|
|
|
;;
|
|
|
|
|
2015-09-02 19:20:58 +02:00
|
|
|
-d|--delete)
|
2016-11-21 14:15:57 +01:00
|
|
|
allow_cmd "${sudo_prefix}btrfs subvolume delete"
|
2016-03-31 15:05:08 +02:00
|
|
|
;;
|
2015-09-02 19:20:58 +02:00
|
|
|
|
|
|
|
-i|--info)
|
2016-11-21 14:15:57 +01:00
|
|
|
allow_cmd "${sudo_prefix}btrfs subvolume find-new"
|
|
|
|
allow_cmd "${sudo_prefix}btrfs filesystem usage"
|
2016-03-31 15:05:08 +02:00
|
|
|
;;
|
2015-09-02 19:20:58 +02:00
|
|
|
|
|
|
|
--snapshot)
|
2016-11-21 14:15:57 +01:00
|
|
|
allow_cmd "${sudo_prefix}btrfs subvolume snapshot"
|
2016-03-31 15:05:08 +02:00
|
|
|
;;
|
2015-09-02 19:20:58 +02:00
|
|
|
|
|
|
|
--send)
|
2016-11-21 14:15:57 +01:00
|
|
|
allow_cmd "${sudo_prefix}btrfs send"
|
2016-03-31 15:05:08 +02:00
|
|
|
;;
|
2015-09-02 19:20:58 +02:00
|
|
|
|
|
|
|
--receive)
|
2016-11-21 14:15:57 +01:00
|
|
|
allow_cmd "${sudo_prefix}btrfs receive"
|
2016-03-31 15:05:08 +02:00
|
|
|
;;
|
2015-09-02 19:20:58 +02:00
|
|
|
|
|
|
|
*)
|
2016-03-31 15:05:08 +02:00
|
|
|
echo "ERROR: ssh_filter_btrbk.sh: failed to parse command line option: $key" 1>&2
|
2019-08-18 13:49:37 +02:00
|
|
|
exit 255
|
2016-03-31 15:05:08 +02:00
|
|
|
;;
|
2015-09-02 19:20:58 +02:00
|
|
|
esac
|
|
|
|
shift
|
|
|
|
done
|
|
|
|
|
2020-02-09 15:47:05 +01:00
|
|
|
# NOTE: subvolume queries are NOT affected by "--restrict-path":
|
2019-11-19 22:07:37 +01:00
|
|
|
# btrbk also calls show/list on the mount point of the subvolume
|
2021-07-14 20:34:40 +02:00
|
|
|
allow_exact_cmd "${sudo_prefix}btrfs subvolume (show|list)( ${option_match})* ${file_arg_match}";
|
2020-05-24 00:13:10 +02:00
|
|
|
allow_cmd "${sudo_prefix}readlink" # resolve symlink
|
2021-07-14 20:34:40 +02:00
|
|
|
allow_exact_cmd "${sudo_prefix}test -d ${file_arg_match}" # check directory (only for compat=busybox)
|
2020-05-24 00:13:10 +02:00
|
|
|
allow_exact_cmd "cat /proc/self/mountinfo" # resolve mountpoints
|
|
|
|
allow_exact_cmd "cat /proc/self/mounts" # legacy, for btrbk < 0.27.0
|
2016-11-21 14:15:57 +01:00
|
|
|
|
2015-09-02 19:20:58 +02:00
|
|
|
# remove leading "|" on alternation lists
|
|
|
|
allow_list=${allow_list#\|}
|
2016-03-31 14:30:53 +02:00
|
|
|
allow_exact_list=${allow_exact_list#\|}
|
2015-09-02 19:20:58 +02:00
|
|
|
restrict_path_list=${restrict_path_list#\|}
|
|
|
|
|
2015-02-09 11:42:44 +01:00
|
|
|
case "$SSH_ORIGINAL_COMMAND" in
|
2019-08-16 01:27:43 +02:00
|
|
|
*\.\./*) reject_and_die 'directory traversal' ;;
|
|
|
|
*\$*) 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 "<"' ;;
|
|
|
|
*\>*) 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 "|"' ;;
|
2015-02-09 11:42:44 +01:00
|
|
|
esac
|
2016-08-18 17:41:26 +02:00
|
|
|
|
|
|
|
reject_filtered_cmd
|
|
|
|
run_cmd
|