1#!/bin/sh
2#* reason                       -- why this script was called, one of: pre-init connect disconnect
3#* VPNGATEWAY                   -- vpn gateway address (always present)
4#* TUNDEV                       -- tunnel device (always present)
5#* INTERNAL_IP4_ADDRESS         -- address (always present)
6#* INTERNAL_IP4_NETMASK         -- netmask (often unset)
7#* INTERNAL_IP4_NETMASKLEN      -- netmask length (often unset)
8#* INTERNAL_IP4_NETADDR         -- address of network (only present if netmask is set)
9#* INTERNAL_IP4_DNS             -- list of dns serverss
10#* INTERNAL_IP4_NBNS            -- list of wins servers
11#* CISCO_DEF_DOMAIN             -- default domain name
12#* CISCO_BANNER                 -- banner from server
13#* CISCO_SPLIT_INC              -- number of networks in split-network-list
14#* CISCO_SPLIT_INC_%d_ADDR      -- network address
15#* CISCO_SPLIT_INC_%d_MASK      -- subnet mask (for example: 255.255.255.0)
16#* CISCO_SPLIT_INC_%d_MASKLEN   -- subnet masklen (for example: 24)
17#* CISCO_SPLIT_INC_%d_PROTOCOL  -- protocol (often just 0)
18#* CISCO_SPLIT_INC_%d_SPORT     -- source port (often just 0)
19#* CISCO_SPLIT_INC_%d_DPORT     -- destination port (often just 0)
20
21# FIXMEs:
22
23# Section A: route handling
24
25# 1) The 3 values CISCO_SPLIT_INC_%d_PROTOCOL/SPORT/DPORT are currently being ignored
26#   In order to use them, we'll probably need os specific solutions
27#   * Linux: iptables -t mangle -I PREROUTING <conditions> -j ROUTE --oif $TUNDEV
28#       This would be an *alternative* to changing the routes (and thus 2) and 3)
29#       shouldn't be relevant at all)
30# 2) There are two different functions to set routes: generic routes and the
31#   default route. Why isn't the defaultroute handled via the generic route case?
32# 3) In the split tunnel case, all routes but the default route might get replaced
33#   without getting restored later. We should explicitely check and save them just
34#   like the defaultroute
35# 4) Replies to a dhcp-server should never be sent into the tunnel
36
37# Section B: Split DNS handling
38
39# 1) Maybe dnsmasq can do something like that
40# 2) Parse dns packets going out via tunnel and redirect them to original dns-server
41
42#env | sort
43#set -x
44
45# =========== script (variable) setup ====================================
46
47PATH=/sbin:/usr/sbin:$PATH
48
49OS="`uname -s`"
50
51DEFAULT_ROUTE_FILE=/var/run/vpnc.defaultroute
52RESOLV_CONF_BACKUP=/var/run/vpnc.resolv.conf-backup
53FULL_SCRIPTNAME=@PREFIX@/sbin/vpnc
54SCRIPTNAME=`basename $FULL_SCRIPTNAME`
55
56# some systems, eg. Darwin & FreeBSD, prune /var/run on boot
57if [ ! -d "/var/run/vpnc" ]; then
58	mkdir -p /var/run/vpnc
59fi
60
61# stupid SunOS: no blubber in /usr/local/bin ... (on stdout)
62IPROUTE="`which ip | grep '^/' 2> /dev/null`"
63
64if [ "$OS" = "Linux" ]; then
65	ifconfig_syntax_ptp="pointopoint"
66	route_syntax_gw="gw"
67	route_syntax_del="del"
68	route_syntax_netmask="netmask"
69else
70	ifconfig_syntax_ptp=""
71	route_syntax_gw=""
72	route_syntax_del="delete"
73	route_syntax_netmask="-netmask"
74fi
75
76# Optional tool on Debian, Ubuntu, Gentoo. FreeBSD 9.0-RELEASE has a different,
77# incompatible /sbin/resolvconf implementation
78if [ -x /sbin/resolvconf ] && [ "$OS" = "Linux" ]; then
79	MODIFYRESOLVCONF=modify_resolvconf_manager
80	RESTORERESOLVCONF=restore_resolvconf_manager
81elif [ -x /sbin/modify_resolvconf ]; then # Mandatory tool on Suse earlier than 11.1
82	MODIFYRESOLVCONF=modify_resolvconf_suse
83	RESTORERESOLVCONF=restore_resolvconf_suse
84else # Generic for any OS
85	MODIFYRESOLVCONF=modify_resolvconf_generic
86	RESTORERESOLVCONF=restore_resolvconf_generic
87fi
88
89# =========== tunnel interface handling ====================================
90
91do_ifconfig() {
92	if [ -n "$INTERNAL_IP4_MTU" ]; then
93		MTU=$INTERNAL_IP4_MTU
94	elif [ -n "$IPROUTE" ]; then
95		DEV=$($IPROUTE route | grep ^default | sed 's/^.* dev \([[:alnum:]-]\+\).*$/\1/')
96		MTU=$(($($IPROUTE link show "$DEV" | grep mtu | sed 's/^.* mtu \([[:digit:]]\+\).*$/\1/') - 88))
97	else
98		MTU=1412
99	fi
100
101	# Point to point interface require a netmask of 255.255.255.255 on some systems
102	ifconfig "$TUNDEV" inet "$INTERNAL_IP4_ADDRESS" $ifconfig_syntax_ptp "$INTERNAL_IP4_ADDRESS" netmask 255.255.255.255 mtu ${MTU} up
103
104	if [ -n "$INTERNAL_IP4_NETMASK" ]; then
105		set_network_route $INTERNAL_IP4_NETADDR $INTERNAL_IP4_NETMASK $INTERNAL_IP4_NETMASKLEN
106	fi
107}
108
109destroy_tun_device() {
110	case "$OS" in
111	NetBSD) # and probably others...
112		ifconfig "$TUNDEV" destroy
113		;;
114	esac
115}
116
117# =========== route handling ====================================
118
119if [ -n "$IPROUTE" ]; then
120	fix_ip_get_output () {
121		sed 's/cache//;s/metric \?[0-9]\+ [0-9]\+//g;s/hoplimit [0-9]\+//g'
122	}
123
124	set_vpngateway_route() {
125		$IPROUTE route add `$IPROUTE route get "$VPNGATEWAY" | fix_ip_get_output`
126		$IPROUTE route flush cache
127	}
128
129	del_vpngateway_route() {
130		$IPROUTE route $route_syntax_del "$VPNGATEWAY"
131		$IPROUTE route flush cache
132	}
133
134	set_default_route() {
135		$IPROUTE route | grep '^default' | fix_ip_get_output > "$DEFAULT_ROUTE_FILE"
136		$IPROUTE route replace default dev "$TUNDEV"
137		$IPROUTE route flush cache
138	}
139
140	set_network_route() {
141		NETWORK="$1"
142		NETMASK="$2"
143		NETMASKLEN="$3"
144		$IPROUTE route replace "$NETWORK/$NETMASKLEN" dev "$TUNDEV"
145		$IPROUTE route flush cache
146	}
147
148	reset_default_route() {
149		if [ -s "$DEFAULT_ROUTE_FILE" ]; then
150			$IPROUTE route replace `cat "$DEFAULT_ROUTE_FILE"`
151			$IPROUTE route flush cache
152			rm -f -- "$DEFAULT_ROUTE_FILE"
153		fi
154	}
155
156	del_network_route() {
157		NETWORK="$1"
158		NETMASK="$2"
159		NETMASKLEN="$3"
160		$IPROUTE route $route_syntax_del "$NETWORK/$NETMASKLEN" dev "$TUNDEV"
161		$IPROUTE route flush cache
162	}
163else # use route command
164	get_default_gw() {
165		# isn't -n supposed to give --numeric output?
166		# apperently not...
167		# Get rid of lines containing IPv6 addresses (':')
168		netstat -r -n | awk '/:/ { next; } /^(default|0\.0\.0\.0)/ { print $2; }'
169	}
170
171	set_vpngateway_route() {
172		route add -host "$VPNGATEWAY" $route_syntax_gw "`get_default_gw`"
173	}
174
175	del_vpngateway_route() {
176		route $route_syntax_del -host "$VPNGATEWAY"
177	}
178
179	set_default_route() {
180		DEFAULTGW="`get_default_gw`"
181		echo "$DEFAULTGW" > "$DEFAULT_ROUTE_FILE"
182		route $route_syntax_del default
183		route add default $route_syntax_gw "$INTERNAL_IP4_ADDRESS"
184	}
185
186	set_network_route() {
187		NETWORK="$1"
188		NETMASK="$2"
189		NETMASKLEN="$3"
190		del_network_route "$NETWORK" "$NETMASK" "$NETMASKLEN"
191		route add -net "$NETWORK" $route_syntax_netmask "$NETMASK" $route_syntax_gw "$INTERNAL_IP4_ADDRESS"
192	}
193
194	reset_default_route() {
195		if [ -s "$DEFAULT_ROUTE_FILE" ]; then
196			route $route_syntax_del default
197			route add default $route_syntax_gw `cat "$DEFAULT_ROUTE_FILE"`
198			rm -f -- "$DEFAULT_ROUTE_FILE"
199		fi
200	}
201
202	del_network_route() {
203		case "$OS" in
204		Linux|NetBSD|Darwin) # and probably others...
205			# routes are deleted automatically on device shutdown
206			return
207			;;
208		esac
209		NETWORK="$1"
210		NETMASK="$2"
211		NETMASKLEN="$3"
212		route $route_syntax_del -net "$NETWORK" $route_syntax_netmask "$NETMASK" $route_syntax_gw "$INTERNAL_IP4_ADDRESS"
213	}
214fi
215
216# =========== resolv.conf handling ====================================
217
218# =========== resolv.conf handling for any OS =========================
219
220modify_resolvconf_generic() {
221	grep '^#@VPNC_GENERATED@' /etc/resolv.conf > /dev/null 2>&1 || cp -- /etc/resolv.conf "$RESOLV_CONF_BACKUP"
222	NEW_RESOLVCONF="#@VPNC_GENERATED@ -- this file is generated by vpnc
223# and will be overwritten by vpnc
224# as long as the above mark is intact"
225
226	# Remember the original value of CISCO_DEF_DOMAIN we need it later
227	CISCO_DEF_DOMAIN_ORIG="$CISCO_DEF_DOMAIN"
228	# Don't step on INTERNAL_IP4_DNS value, use a temporary variable
229	INTERNAL_IP4_DNS_TEMP="$INTERNAL_IP4_DNS"
230	exec 6< "$RESOLV_CONF_BACKUP"
231	while read LINE <&6 ; do
232		case "$LINE" in
233			nameserver*)
234				if [ -n "$INTERNAL_IP4_DNS_TEMP" ]; then
235					read ONE_NAMESERVER INTERNAL_IP4_DNS_TEMP <<-EOF
236	$INTERNAL_IP4_DNS_TEMP
237EOF
238					LINE="nameserver $ONE_NAMESERVER"
239				else
240					LINE=""
241				fi
242				;;
243			search*)
244				if [ -n "$CISCO_DEF_DOMAIN" ]; then
245					LINE="$LINE $CISCO_DEF_DOMAIN"
246					CISCO_DEF_DOMAIN=""
247				fi
248				;;
249			domain*)
250				if [ -n "$CISCO_DEF_DOMAIN" ]; then
251					LINE="domain $CISCO_DEF_DOMAIN"
252					CISCO_DEF_DOMAIN=""
253				fi
254				;;
255		esac
256		NEW_RESOLVCONF="$NEW_RESOLVCONF
257$LINE"
258	done
259	exec 6<&-
260
261	for i in $INTERNAL_IP4_DNS_TEMP ; do
262		NEW_RESOLVCONF="$NEW_RESOLVCONF
263nameserver $i"
264	done
265	if [ -n "$CISCO_DEF_DOMAIN" ]; then
266		NEW_RESOLVCONF="$NEW_RESOLVCONF
267search $CISCO_DEF_DOMAIN"
268	fi
269	echo "$NEW_RESOLVCONF" > /etc/resolv.conf
270
271	if [ "$OS" = "Darwin" ]; then
272		case "`uname -r`" in
273			# Skip for pre-10.4 systems
274			4.*|5.*|6.*|7.*)
275				;;
276			# 10.4 and later require use of scutil for DNS to work properly
277			*)
278				OVERRIDE_PRIMARY=""
279				if [ -n "$CISCO_SPLIT_INC" ]; then
280					if [ $CISCO_SPLIT_INC -lt 1 ]; then
281						# Must override for correct default route
282						# Cannot use multiple DNS matching in this case
283						OVERRIDE_PRIMARY='d.add OverridePrimary # 1'
284					fi
285				fi
286				# Uncomment the following if/fi pair to use multiple
287				# DNS matching when available.  When multiple DNS matching
288				# is present, anything reading the /etc/resolv.conf file
289				# directly will probably not work as intended.
290				#if [ -z "$CISCO_DEF_DOMAIN_ORIG" ]; then
291					# Cannot use multiple DNS matching without a domain
292					OVERRIDE_PRIMARY='d.add OverridePrimary # 1'
293				#fi
294				scutil >/dev/null 2>&1 <<-EOF
295					open
296					d.init
297					d.add ServerAddresses * $INTERNAL_IP4_DNS
298					set State:/Network/Service/$TUNDEV/DNS
299					d.init
300					# next line overrides the default gateway and breaks split routing
301					# d.add Router $INTERNAL_IP4_ADDRESS
302					d.add Addresses * $INTERNAL_IP4_ADDRESS
303					d.add SubnetMasks * 255.255.255.255
304					d.add InterfaceName $TUNDEV
305					$OVERRIDE_PRIMARY
306					set State:/Network/Service/$TUNDEV/IPv4
307					close
308				EOF
309				if [ -n "$CISCO_DEF_DOMAIN_ORIG" ]; then
310					scutil >/dev/null 2>&1 <<-EOF
311						open
312						get State:/Network/Service/$TUNDEV/DNS
313						d.add DomainName $CISCO_DEF_DOMAIN_ORIG
314						d.add SearchDomains * $CISCO_DEF_DOMAIN_ORIG
315						d.add SupplementalMatchDomains * $CISCO_DEF_DOMAIN_ORIG
316						set State:/Network/Service/$TUNDEV/DNS
317						close
318					EOF
319				fi
320				;;
321		esac
322	fi
323}
324
325restore_resolvconf_generic() {
326	if [ ! -e "$RESOLV_CONF_BACKUP" ]; then
327		return
328	fi
329	grep '^#@VPNC_GENERATED@' /etc/resolv.conf > /dev/null 2>&1 && cat "$RESOLV_CONF_BACKUP" > /etc/resolv.conf
330	rm -f -- "$RESOLV_CONF_BACKUP"
331
332	if [ "$OS" = "Darwin" ]; then
333		case "`uname -r`" in
334			# Skip for pre-10.4 systems
335			4.*|5.*|6.*|7.*)
336				;;
337			# 10.4 and later require use of scutil for DNS to work properly
338			*)
339				scutil >/dev/null 2>&1 <<-EOF
340					open
341					remove State:/Network/Service/$TUNDEV/IPv4
342					remove State:/Network/Service/$TUNDEV/DNS
343					close
344				EOF
345				;;
346		esac
347	fi
348}
349# === resolv.conf handling via /sbin/modify_resolvconf (Suse) =====================
350
351# Suse provides a script that modifies resolv.conf. Use it because it will
352# restart/reload all other services that care about it (e.g. lwresd).
353
354modify_resolvconf_suse()
355{
356	RESOLV_OPTS=''
357	test -n "$INTERNAL_IP4_DNS" && RESOLV_OPTS="-n \"$INTERNAL_IP4_DNS\""
358	test -n "$CISCO_DEF_DOMAIN" && RESOLV_OPTS="$RESOLV_OPTS -d $CISCO_DEF_DOMAIN"
359	test -n "$RESOLV_OPTS" && eval /sbin/modify_resolvconf modify -s $SCRIPTNAME -p $SCRIPTNAME -f $FULL_SCRIPTNAME -e $TUNDEV $RESOLV_OPTS -t \"This file was created by $SCRIPTNAME\"
360}
361
362# Restore resolv.conf to old contents on Suse
363restore_resolvconf_suse()
364{
365	/sbin/modify_resolvconf restore -s vpnc -p $SCRIPTNAME -f $FULL_SCRIPTNAME -e $TUNDEV
366}
367
368# === resolv.conf handling via /sbin/resolvconf (Debian, Ubuntu, Gentoo)) =========
369
370modify_resolvconf_manager() {
371	NEW_RESOLVCONF=""
372	for i in $INTERNAL_IP4_DNS; do
373		NEW_RESOLVCONF="$NEW_RESOLVCONF
374nameserver $i"
375	done
376	if [ -n "$CISCO_DEF_DOMAIN" ]; then
377		NEW_RESOLVCONF="$NEW_RESOLVCONF
378domain $CISCO_DEF_DOMAIN"
379	fi
380	echo "$NEW_RESOLVCONF" | /sbin/resolvconf -a $TUNDEV
381}
382
383restore_resolvconf_manager() {
384	/sbin/resolvconf -d $TUNDEV
385}
386
387# ========= Toplevel state handling  =======================================
388
389kernel_is_2_6_or_above() {
390	case `uname -r` in
391		1.*|2.[012345]*)
392			return 1
393			;;
394		*)
395			return 0
396			;;
397	esac
398}
399
400do_pre_init() {
401	if [ "$OS" = "Linux" ]; then
402		if (exec 6<> /dev/net/tun) > /dev/null 2>&1 ; then
403			:
404		else # can't open /dev/net/tun
405			test -e /proc/sys/kernel/modprobe && `cat /proc/sys/kernel/modprobe` tun 2>/dev/null
406			# fix for broken devfs in kernel 2.6.x
407			if [ "`readlink /dev/net/tun`" = misc/net/tun \
408				-a ! -e /dev/net/misc/net/tun -a -e /dev/misc/net/tun ] ; then
409				ln -sf /dev/misc/net/tun /dev/net/tun
410			fi
411			# make sure tun device exists
412			if [ ! -e /dev/net/tun ]; then
413				mkdir -p /dev/net
414				mknod -m 0640 /dev/net/tun c 10 200
415			fi
416			# workaround for a possible latency caused by udev, sleep max. 10s
417			if kernel_is_2_6_or_above ; then
418				for x in `seq 100` ; do
419					(exec 6<> /dev/net/tun) > /dev/null 2>&1 && break;
420					sleep 0.1
421				done
422			fi
423		fi
424	elif [ "$OS" = "FreeBSD" ]; then
425		kldstat -q -m if_tun
426		if [ ${?} != 0 ]; then
427			kldload if_tun
428		fi
429	elif [ "$OS" = "GNU/kFreeBSD" ]; then
430		kldstat -q -m if_tun
431		if [ ${?} != 0 ]; then
432			kldload if_tun
433		fi
434	elif [ "$OS" = "NetBSD" ]; then
435		:
436	elif [ "$OS" = "OpenBSD" ]; then
437		:
438	elif [ "$OS" = "SunOS" ]; then
439		:
440	elif [ "$OS" = "Darwin" ]; then
441		:
442	fi
443}
444
445do_connect() {
446	if [ -n "$CISCO_BANNER" ]; then
447		echo "Connect Banner:"
448		echo "$CISCO_BANNER" | while read LINE ; do echo "|" "$LINE" ; done
449		echo
450	fi
451
452	do_ifconfig
453	set_vpngateway_route
454	if [ -n "$CISCO_SPLIT_INC" ]; then
455		i=0
456		while [ $i -lt $CISCO_SPLIT_INC ] ; do
457			eval NETWORK="\${CISCO_SPLIT_INC_${i}_ADDR}"
458			eval NETMASK="\${CISCO_SPLIT_INC_${i}_MASK}"
459			eval NETMASKLEN="\${CISCO_SPLIT_INC_${i}_MASKLEN}"
460			if [ $NETWORK != "0.0.0.0" ]; then
461				set_network_route "$NETWORK" "$NETMASK" "$NETMASKLEN"
462			else
463				set_default_route
464			fi
465			i=`expr $i + 1`
466		done
467		for i in $INTERNAL_IP4_DNS ; do
468			set_network_route "$i" "255.255.255.255" "32"
469		done
470	else
471		set_default_route
472	fi
473
474	if [ -n "$INTERNAL_IP4_DNS" ]; then
475		$MODIFYRESOLVCONF
476	fi
477}
478
479do_disconnect() {
480	if [ -n "$CISCO_SPLIT_INC" ]; then
481		i=0
482		while [ $i -lt $CISCO_SPLIT_INC ] ; do
483			eval NETWORK="\${CISCO_SPLIT_INC_${i}_ADDR}"
484			eval NETMASK="\${CISCO_SPLIT_INC_${i}_MASK}"
485			eval NETMASKLEN="\${CISCO_SPLIT_INC_${i}_MASKLEN}"
486			if [ $NETWORK != "0.0.0.0" ]; then
487				# FIXME: This doesn't restore previously overwritten
488				#        routes.
489				del_network_route "$NETWORK" "$NETMASK" "$NETMASKLEN"
490			else
491				reset_default_route
492			fi
493			i=`expr $i + 1`
494		done
495		for i in $INTERNAL_IP4_DNS ; do
496			del_network_route "$i" "255.255.255.255" "32"
497		done
498	else
499		reset_default_route
500	fi
501
502	del_vpngateway_route
503
504	if [ -n "$INTERNAL_IP4_DNS" ]; then
505		$RESTORERESOLVCONF
506	fi
507	destroy_tun_device
508}
509
510#### Main
511
512if [ -z "$reason" ]; then
513	echo "this script must be called from vpnc" 1>&2
514	exit 1
515fi
516
517case "$reason" in
518	pre-init)
519		do_pre_init
520		;;
521	connect)
522		do_connect
523		;;
524	disconnect)
525		do_disconnect
526		;;
527	*)
528		echo "unknown reason '$reason'. Maybe vpnc-script is out of date" 1>&2
529		exit 1
530		;;
531esac
532
533exit 0
534