summaryrefslogtreecommitdiffstats
path: root/rsync-ssl
blob: 56ee7dfe0129b8bbffd7f77f3561d4667a34d723 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#!/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_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

    if [[ $RSYNC_SSL_TYPE == openssl ]]; then
	exec $RSYNC_SSL_OPENSSL s_client $caopt $certopt $keyopt -quiet -verify_quiet -servername $hostname -verify_hostname $hostname -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
$certopt
$cafile
EOF
    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

if [[ "$1" == --type=* ]]; then
    export RSYNC_SSL_TYPE="${1/--type=/}"
    shift
fi

rsync_ssl_run "${@}"