#!/usr/bin/env bash# This script uses openssl, gnutls, or stunnel to secure an rsync daemon connection.# By default this script takes rsync args and hands them off to the actual# rsync command with an --rsh option that makes it open an SSL connection to an# rsync daemon. See the rsync-ssl manpage for usage details and env variables.# When the first arg is --HELPER, we are being used by rsync as an --rsh helper# script, and the args are (note the trailing dot):## rsync-ssl --HELPER HOSTNAME rsync --server --daemon .## --HELPER is not a user-facing option, so it is not documented in the manpage.# The first SSL setup was based on: http://dozzie.jarowit.net/trac/wiki/RsyncSSL# Note that an stunnel connection requires at least version 4.x of stunnel.function rsync_ssl_run { case "$*" in *rsync://*) ;; *::*) ;; *) echo "You must use rsync-ssl with a daemon-style hostname." 1>&2 exit 1 ;; esac exec rsync --rsh="'$0' --HELPER" "${@}"}function rsync_ssl_helper { if [[ -z "$RSYNC_SSL_TYPE" ]]; then found=`path_search openssl stunnel4 stunnel` || exit 1 if [[ "$found" == */openssl ]]; then RSYNC_SSL_TYPE=openssl RSYNC_SSL_OPENSSL="$found" elif [[ "$found" == */gnutls-cli ]]; then RSYNC_SSL_TYPE=gnutls RSYNC_SSL_GNUTLS="$found" else RSYNC_SSL_TYPE=stunnel RSYNC_SSL_STUNNEL="$found" fi fi case "$RSYNC_SSL_TYPE" in openssl) if [[ -z "$RSYNC_SSL_OPENSSL" ]]; then RSYNC_SSL_OPENSSL=`path_search openssl` || exit 1 fi optsep=' ' ;; gnutls) if [[ -z "$RSYNC_SSL_GNUTLS" ]]; then RSYNC_SSL_GNUTLS=`path_search gnutls-cli` || exit 1 fi optsep=' ' ;; stunnel) if [[ -z "$RSYNC_SSL_STUNNEL" ]]; then RSYNC_SSL_STUNNEL=`path_search stunnel4 stunnel` || exit 1 fi optsep=' = ' ;; *) echo "The RSYNC_SSL_TYPE specifies an unknown type: $RSYNC_SSL_TYPE" 1>&2 exit 1 ;; esac if [[ -z "$RSYNC_SSL_CERT" ]]; then certopt="" gnutls_cert_opt="" else certopt="-cert$optsep$RSYNC_SSL_CERT" gnutls_cert_opt="--x509certfile=$RSYNC_SSL_CERT" fi if [[ -z "$RSYNC_SSL_KEY" ]]; then keyopt="" gnutls_key_opt="" else keyopt="-key$optsep$RSYNC_SSL_KEY" gnutls_key_opt="--x509keyfile=$RSYNC_SSL_KEY" fi if [[ -z ${RSYNC_SSL_CA_CERT+x} ]]; then # RSYNC_SSL_CA_CERT unset - default CA set AND verify: # openssl: caopt="-verify_return_error -verify 4" # gnutls: # gnutls-cli has no reliable "use the system trust store AND make # verification fatal" switch across versions, so the gnutls path # refuses below unless RSYNC_SSL_ALLOW_INSECURE_GNUTLS is set. gnutls_opts="" # stunnel: # Since there is no way of using the default CA certificate collection, # we cannot do any verification. Thus, stunnel should really only be # used if nothing else is available. cafile="" verify="" elif [[ "$RSYNC_SSL_CA_CERT" == "" ]]; then # RSYNC_SSL_CA_CERT set but empty -do NO verifications: # openssl: caopt="-verify 1" # gnutls: gnutls_opts="--insecure" # stunnel: cafile="" verify="verifyChain = no" else # RSYNC_SSL_CA_CERT set - use CA AND verify: # openssl: caopt="-CAfile $RSYNC_SSL_CA_CERT -verify_return_error -verify 4" # gnutls: gnutls_opts="--x509cafile=$RSYNC_SSL_CA_CERT" # stunnel: cafile="CAfile = $RSYNC_SSL_CA_CERT" verify="verifyChain = yes" fi port="${RSYNC_PORT:-0}" if [[ "$port" == 0 ]]; then port="${RSYNC_SSL_PORT:-874}" fi # If the user specified USER@HOSTNAME::module, then rsync passes us # the -l USER option too, so we must be prepared to ignore it. if [[ "$1" == "-l" ]]; then shift 2 fi hostname="$1" shift if [[ -z "$hostname" || "$1" != rsync || "$2" != --server || "$3" != --daemon ]]; then echo "Usage: rsync-ssl --HELPER HOSTNAME rsync --server --daemon ." 1>&2 exit 1 fi validate_ssl_hostname "$hostname" if [[ $RSYNC_SSL_TYPE == stunnel && -z ${RSYNC_SSL_CA_CERT+x} && "$RSYNC_SSL_ALLOW_INSECURE_STUNNEL" != 1 ]]; then echo "stunnel requires RSYNC_SSL_CA_CERT for server certificate validation (or set RSYNC_SSL_ALLOW_INSECURE_STUNNEL=1 to opt out)" 1>&2 exit 1 fi # Bind the server certificate to the requested host (openssl -verify_hostname, # stunnel checkHost) so a cert that is CA-valid for a *different* name can't be # used to MITM. Set RSYNC_SSL_SKIP_HOSTNAME_CHECK=1 to keep CA chain # verification but skip the identity check (e.g. bare-IP or internal-CA setups). # checkHost only applies while the chain is being verified. openssl_host_opt="-verify_hostname $hostname" stunnel_host_opt="" [[ "$verify" == "verifyChain = yes" ]] && stunnel_host_opt="checkHost = $hostname" if [[ "$RSYNC_SSL_SKIP_HOSTNAME_CHECK" == 1 ]]; then openssl_host_opt="" stunnel_host_opt="" fi if [[ $RSYNC_SSL_TYPE == gnutls && -z ${RSYNC_SSL_CA_CERT+x} && "$RSYNC_SSL_ALLOW_INSECURE_GNUTLS" != 1 ]]; then echo "gnutls-cli requires RSYNC_SSL_CA_CERT for server certificate validation (or set RSYNC_SSL_ALLOW_INSECURE_GNUTLS=1 to opt out)" 1>&2 exit 1 fi if [[ $RSYNC_SSL_TYPE == openssl ]]; then exec "$RSYNC_SSL_OPENSSL" s_client $caopt $certopt $keyopt -quiet -verify_quiet -servername $hostname $openssl_host_opt -connect $hostname:$port elif [[ $RSYNC_SSL_TYPE == gnutls ]]; then exec "$RSYNC_SSL_GNUTLS" --logfile=/dev/null $gnutls_cert_opt $gnutls_key_opt $gnutls_opts $hostname:$port else # devzero@web.de came up with this no-tmpfile calling syntax: exec "$RSYNC_SSL_STUNNEL" -fd 10 11<&0 <<EOF 10<&0 0<&11 11<&-foreground = yesdebug = critconnect = $hostname:$portclient = yesTIMEOUTclose = 0$verify$stunnel_host_opt$certopt$cafileEOF fi}function validate_ssl_hostname { local host="$1" if [[ -z "$host" || "$host" == -* || "$host" =~ [^A-Za-z0-9._:-] ]]; then echo "invalid rsync-ssl hostname: $host" 1>&2 exit 1 fi}function path_search { IFS_SAVE="$IFS" IFS=: for prog in "${@}"; do for dir in $PATH; do [[ -z "$dir" ]] && dir=. if [[ -f "$dir/$prog" && -x "$dir/$prog" ]]; then echo "$dir/$prog" IFS="$IFS_SAVE" return 0 fi done done IFS="$IFS_SAVE" echo "Failed to find on your path: $*" 1>&2 echo "See the rsync-ssl manpage for configuration assistance." 1>&2 return 1}if [[ "$#" == 0 ]]; then echo "Usage: rsync-ssl [--type=SSL_TYPE] RSYNC_ARG [...]" 1>&2 echo "The SSL_TYPE can be openssl or stunnel" exit 1fiif [[ "$1" = --help || "$1" = -h ]]; then exec rsync --helpfiif [[ "$1" == --HELPER ]]; then shift rsync_ssl_helper "${@}"fiargs=()dash_dash_seen=falsefor arg in "$@"; do if [[ $dash_dash_seen == true ]]; then args+=("$arg") elif [[ "$arg" == "--" ]]; then args+=("$arg") dash_dash_seen=true elif [[ "$arg" == --type=* ]]; then export RSYNC_SSL_TYPE="${arg#--type=}" else args+=("$arg") fidoneset -- "${args[@]}"rsync_ssl_run "${@}"
#!/usr/bin/env bash
# This script uses openssl, gnutls, or stunnel to secure an rsync daemon connection.
# By default this script takes rsync args and hands them off to the actual
# rsync command with an --rsh option that makes it open an SSL connection to an
# rsync daemon. See the rsync-ssl manpage for usage details and env variables.
# When the first arg is --HELPER, we are being used by rsync as an --rsh helper
# script, and the args are (note the trailing dot):
#
# rsync-ssl --HELPER HOSTNAME rsync --server --daemon .
#
# --HELPER is not a user-facing option, so it is not documented in the manpage.
# The first SSL setup was based on: http://dozzie.jarowit.net/trac/wiki/RsyncSSL
# Note that an stunnel connection requires at least version 4.x of stunnel.
function rsync_ssl_run {
case "$*" in
*rsync://*) ;;
*::*) ;;
*)
echo "You must use rsync-ssl with a daemon-style hostname." 1>&2
exit 1
;;
esac
exec rsync --rsh="'$0' --HELPER" "${@}"
}
function rsync_ssl_helper {
if [[ -z "$RSYNC_SSL_TYPE" ]]; then
found=`path_search openssl stunnel4 stunnel` || exit 1
if [[ "$found" == */openssl ]]; then
RSYNC_SSL_TYPE=openssl
RSYNC_SSL_OPENSSL="$found"
elif [[ "$found" == */gnutls-cli ]]; then
RSYNC_SSL_TYPE=gnutls
RSYNC_SSL_GNUTLS="$found"
else
RSYNC_SSL_TYPE=stunnel
RSYNC_SSL_STUNNEL="$found"
fi
fi
case "$RSYNC_SSL_TYPE" in
openssl)
if [[ -z "$RSYNC_SSL_OPENSSL" ]]; then
RSYNC_SSL_OPENSSL=`path_search openssl` || exit 1
fi
optsep=' '
;;
gnutls)
if [[ -z "$RSYNC_SSL_GNUTLS" ]]; then
RSYNC_SSL_GNUTLS=`path_search gnutls-cli` || exit 1
fi
optsep=' '
;;
stunnel)
if [[ -z "$RSYNC_SSL_STUNNEL" ]]; then
RSYNC_SSL_STUNNEL=`path_search stunnel4 stunnel` || exit 1
fi
optsep=' = '
;;
*)
echo "The RSYNC_SSL_TYPE specifies an unknown type: $RSYNC_SSL_TYPE" 1>&2
exit 1
;;
esac
if [[ -z "$RSYNC_SSL_CERT" ]]; then
certopt=""
gnutls_cert_opt=""
else
certopt="-cert$optsep$RSYNC_SSL_CERT"
gnutls_cert_opt="--x509certfile=$RSYNC_SSL_CERT"
fi
if [[ -z "$RSYNC_SSL_KEY" ]]; then
keyopt=""
gnutls_key_opt=""
else
keyopt="-key$optsep$RSYNC_SSL_KEY"
gnutls_key_opt="--x509keyfile=$RSYNC_SSL_KEY"
fi
if [[ -z ${RSYNC_SSL_CA_CERT+x} ]]; then
# RSYNC_SSL_CA_CERT unset - default CA set AND verify:
# openssl:
caopt="-verify_return_error -verify 4"
# gnutls:
# gnutls-cli has no reliable "use the system trust store AND make
# verification fatal" switch across versions, so the gnutls path
# refuses below unless RSYNC_SSL_ALLOW_INSECURE_GNUTLS is set.
gnutls_opts=""
# stunnel:
# Since there is no way of using the default CA certificate collection,
# we cannot do any verification. Thus, stunnel should really only be
# used if nothing else is available.
cafile=""
verify=""
elif [[ "$RSYNC_SSL_CA_CERT" == "" ]]; then
# RSYNC_SSL_CA_CERT set but empty -do NO verifications:
# openssl:
caopt="-verify 1"
# gnutls:
gnutls_opts="--insecure"
# stunnel:
cafile=""
verify="verifyChain = no"
else
# RSYNC_SSL_CA_CERT set - use CA AND verify:
# openssl:
caopt="-CAfile $RSYNC_SSL_CA_CERT -verify_return_error -verify 4"
# gnutls:
gnutls_opts="--x509cafile=$RSYNC_SSL_CA_CERT"
# stunnel:
cafile="CAfile = $RSYNC_SSL_CA_CERT"
verify="verifyChain = yes"
fi
port="${RSYNC_PORT:-0}"
if [[ "$port" == 0 ]]; then
port="${RSYNC_SSL_PORT:-874}"
fi
# If the user specified USER@HOSTNAME::module, then rsync passes us
# the -l USER option too, so we must be prepared to ignore it.
if [[ "$1" == "-l" ]]; then
shift 2
fi
hostname="$1"
shift
if [[ -z "$hostname" || "$1" != rsync || "$2" != --server || "$3" != --daemon ]]; then
echo "Usage: rsync-ssl --HELPER HOSTNAME rsync --server --daemon ." 1>&2
exit 1
fi
validate_ssl_hostname "$hostname"
if [[ $RSYNC_SSL_TYPE == stunnel && -z ${RSYNC_SSL_CA_CERT+x} && "$RSYNC_SSL_ALLOW_INSECURE_STUNNEL" != 1 ]]; then
echo "stunnel requires RSYNC_SSL_CA_CERT for server certificate validation (or set RSYNC_SSL_ALLOW_INSECURE_STUNNEL=1 to opt out)" 1>&2
exit 1
fi
# Bind the server certificate to the requested host (openssl -verify_hostname,
# stunnel checkHost) so a cert that is CA-valid for a *different* name can't be
# used to MITM. Set RSYNC_SSL_SKIP_HOSTNAME_CHECK=1 to keep CA chain
# verification but skip the identity check (e.g. bare-IP or internal-CA setups).
# checkHost only applies while the chain is being verified.
openssl_host_opt="-verify_hostname $hostname"
stunnel_host_opt=""
[[ "$verify" == "verifyChain = yes" ]] && stunnel_host_opt="checkHost = $hostname"
if [[ "$RSYNC_SSL_SKIP_HOSTNAME_CHECK" == 1 ]]; then
openssl_host_opt=""
stunnel_host_opt=""
fi
if [[ $RSYNC_SSL_TYPE == gnutls && -z ${RSYNC_SSL_CA_CERT+x} && "$RSYNC_SSL_ALLOW_INSECURE_GNUTLS" != 1 ]]; then
echo "gnutls-cli requires RSYNC_SSL_CA_CERT for server certificate validation (or set RSYNC_SSL_ALLOW_INSECURE_GNUTLS=1 to opt out)" 1>&2
exit 1
fi
if [[ $RSYNC_SSL_TYPE == openssl ]]; then
exec "$RSYNC_SSL_OPENSSL" s_client $caopt $certopt $keyopt -quiet -verify_quiet -servername $hostname $openssl_host_opt -connect $hostname:$port
elif [[ $RSYNC_SSL_TYPE == gnutls ]]; then
exec "$RSYNC_SSL_GNUTLS" --logfile=/dev/null $gnutls_cert_opt $gnutls_key_opt $gnutls_opts $hostname:$port
else
# devzero@web.de came up with this no-tmpfile calling syntax:
exec "$RSYNC_SSL_STUNNEL" -fd 10 11<&0 <<EOF 10<&0 0<&11 11<&-
foreground = yes
debug = crit
connect = $hostname:$port
client = yes
TIMEOUTclose = 0
$verify
$stunnel_host_opt
$certopt
$cafile
EOF
fi
}
function validate_ssl_hostname {
local host="$1"
if [[ -z "$host" || "$host" == -* || "$host" =~ [^A-Za-z0-9._:-] ]]; then
echo "invalid rsync-ssl hostname: $host" 1>&2
exit 1
fi
}
function path_search {
IFS_SAVE="$IFS"
IFS=:
for prog in "${@}"; do
for dir in $PATH; do
[[ -z "$dir" ]] && dir=.
if [[ -f "$dir/$prog" && -x "$dir/$prog" ]]; then
echo "$dir/$prog"
IFS="$IFS_SAVE"
return 0
fi
done
done
IFS="$IFS_SAVE"
echo "Failed to find on your path: $*" 1>&2
echo "See the rsync-ssl manpage for configuration assistance." 1>&2
return 1
}
if [[ "$#" == 0 ]]; then
echo "Usage: rsync-ssl [--type=SSL_TYPE] RSYNC_ARG [...]" 1>&2
echo "The SSL_TYPE can be openssl or stunnel"
exit 1
fi
if [[ "$1" = --help || "$1" = -h ]]; then
exec rsync --help
fi
if [[ "$1" == --HELPER ]]; then
shift
rsync_ssl_helper "${@}"
fi
args=()
dash_dash_seen=false
for arg in "$@"; do
if [[ $dash_dash_seen == true ]]; then
args+=("$arg")
elif [[ "$arg" == "--" ]]; then
args+=("$arg")
dash_dash_seen=true
elif [[ "$arg" == --type=* ]]; then
export RSYNC_SSL_TYPE="${arg#--type=}"
else
args+=("$arg")
fi
done
set -- "${args[@]}"
rsync_ssl_run "${@}"