xref: /freebsd/contrib/openresolv/resolvconf.in (revision 1f4bcc459a76b7aa664f3fd557684cd0ba6da352)
1#!/bin/sh
2# Copyright (c) 2007-2015 Roy Marples
3# All rights reserved
4
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions
7# are met:
8#     * Redistributions of source code must retain the above copyright
9#       notice, this list of conditions and the following disclaimer.
10#     * Redistributions in binary form must reproduce the above
11#       copyright notice, this list of conditions and the following
12#       disclaimer in the documentation and/or other materials provided
13#       with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27RESOLVCONF="$0"
28SYSCONFDIR=@SYSCONFDIR@
29LIBEXECDIR=@LIBEXECDIR@
30VARDIR=@VARDIR@
31
32# Disregard dhcpcd setting
33unset interface_order state_dir
34
35# If you change this, change the test in VFLAG and libc.in as well
36local_nameservers="127.* 0.0.0.0 255.255.255.255 ::1"
37
38dynamic_order="tap[0-9]* tun[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]*"
39interface_order="lo lo[0-9]*"
40name_server_blacklist="0.0.0.0"
41
42# Support original resolvconf configuration layout
43# as well as the openresolv config file
44if [ -f "$SYSCONFDIR"/resolvconf.conf ]; then
45	. "$SYSCONFDIR"/resolvconf.conf
46	[ -n "$state_dir" ] && VARDIR="$state_dir"
47elif [ -d "$SYSCONFDIR/resolvconf" ]; then
48	SYSCONFDIR="$SYSCONFDIR/resolvconf"
49	if [ -f "$SYSCONFDIR"/interface-order ]; then
50		interface_order="$(cat "$SYSCONFDIR"/interface-order)"
51	fi
52fi
53IFACEDIR="$VARDIR/interfaces"
54METRICDIR="$VARDIR/metrics"
55PRIVATEDIR="$VARDIR/private"
56EXCLUSIVEDIR="$VARDIR/exclusive"
57LOCKDIR="$VARDIR/lock"
58
59warn()
60{
61	echo "$*" >&2
62}
63
64error_exit()
65{
66	echo "$*" >&2
67	exit 1
68}
69
70usage()
71{
72	cat <<-EOF
73	Usage: ${RESOLVCONF##*/} [options]
74
75	Inform the system about any DNS updates.
76
77	Options:
78	  -a \$INTERFACE    Add DNS information to the specified interface
79	                   (DNS supplied via stdin in resolv.conf format)
80	  -m metric        Give the added DNS information a metric
81	  -p               Mark the interface as private
82	  -x               Mark the interface as exclusive
83	  -d \$INTERFACE    Delete DNS information from the specified interface
84	  -f               Ignore non existant interfaces
85	  -I               Init the state dir
86	  -u               Run updates from our current DNS information
87	  -l [\$PATTERN]    Show DNS information, optionally from interfaces
88	                   that match the specified pattern
89	  -i [\$PATTERN]    Show interfaces that have supplied DNS information
90                   optionally from interfaces that match the specified
91                   pattern
92	  -v [\$PATTERN]    echo NEWDOMAIN, NEWSEARCH and NEWNS variables to
93	  		   the console
94	  -h               Show this help cruft
95	EOF
96	[ -z "$1" ] && exit 0
97	echo
98	error_exit "$*"
99}
100
101echo_resolv()
102{
103	local line= OIFS="$IFS"
104
105	[ -n "$1" -a -f "$IFACEDIR/$1" ] || return 1
106	echo "# resolv.conf from $1"
107	# Our variable maker works of the fact each resolv.conf per interface
108	# is separated by blank lines.
109	# So we remove them when echoing them.
110	while read -r line; do
111		IFS="$OIFS"
112		if [ -n "$line" ]; then
113			# We need to set IFS here to preserve any whitespace
114			IFS=''
115			printf "%s\n" "$line"
116		fi
117	done < "$IFACEDIR/$1"
118	echo
119	IFS="$OIFS"
120}
121
122# Parse resolv.conf's and make variables
123# for domain name servers, search name servers and global nameservers
124parse_resolv()
125{
126	local line= ns= ds= search= d= n= newns=
127	local new=true iface= private=false p= domain= l= islocal=
128
129	newns=
130
131	while read -r line; do
132		case "$line" in
133		"# resolv.conf from "*)
134			if ${new}; then
135				iface="${line#\# resolv.conf from *}"
136				new=false
137				if [ -e "$PRIVATEDIR/$iface" ]; then
138					private=true
139				else
140					# Allow expansion
141					cd "$IFACEDIR"
142					private=false
143					for p in $private_interfaces; do
144						case "$iface" in
145						"$p"|"$p":*) private=true; break;;
146						esac
147					done
148				fi
149			fi
150			;;
151		"nameserver "*)
152			islocal=false
153			for l in $local_nameservers; do
154				case "${line#* }" in
155				$l)
156					islocal=true
157					echo "LOCALNAMESERVERS=\"\$LOCALNAMESERVERS ${line#* }\""
158					break
159					;;
160				esac
161			done
162			$islocal || ns="$ns${line#* } "
163			;;
164		"domain "*)
165			if [ -z "$domain" ]; then
166				domain="${line#* }"
167				echo "DOMAIN=\"$domain\""
168			fi
169			search="${line#* }"
170			;;
171		"search "*)
172			search="${line#* }"
173			;;
174		*)
175			[ -n "$line" ] && continue
176			if [ -n "$ns" -a -n "$search" ]; then
177				newns=
178				for n in $ns; do
179					newns="$newns${newns:+,}$n"
180				done
181				ds=
182				for d in $search; do
183					ds="$ds${ds:+ }$d:$newns"
184				done
185				echo "DOMAINS=\"\$DOMAINS $ds\""
186			fi
187			echo "SEARCH=\"\$SEARCH $search\""
188			if ! $private; then
189				echo "NAMESERVERS=\"\$NAMESERVERS $ns\""
190			fi
191			ns=
192			search=
193			new=true
194			;;
195		esac
196	done
197}
198
199uniqify()
200{
201	local result=
202	while [ -n "$1" ]; do
203		case " $result " in
204		*" $1 "*);;
205		*) result="$result $1";;
206		esac
207		shift
208	done
209	echo "${result# *}"
210}
211
212dirname()
213{
214	local dir= OIFS="$IFS"
215	local IFS=/
216	set -- $@
217	IFS="$OIFS"
218	if [ -n "$1" ]; then
219		printf %s .
220	else
221		shift
222	fi
223	while [ -n "$2" ]; do
224		printf "/%s" "$1"
225		shift
226	done
227	printf "\n"
228}
229
230config_mkdirs()
231{
232	local e=0 f d
233	for f; do
234		[ -n "$f" ] || continue
235		d="$(dirname "$f")"
236		if [ ! -d "$d" ]; then
237			if type install >/dev/null 2>&1; then
238				install -d "$d" || e=$?
239			else
240				mkdir "$d" || e=$?
241			fi
242		fi
243	done
244	return $e
245}
246
247list_resolv()
248{
249	[ -d "$IFACEDIR" ] || return 0
250
251	local report=false list= retval=0 cmd="$1" excl=
252	shift
253
254	case "$IF_EXCLUSIVE" in
255	[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
256		if [ -d "$EXCLUSIVEDIR" ]; then
257			cd "$EXCLUSIVEDIR"
258			for i in *; do
259				if [ -f "$i" ]; then
260					list="${i#* }"
261					break
262				fi
263			done
264		fi
265		excl=true
266		;;
267	*)
268		excl=false
269		;;
270	esac
271
272	# If we have an interface ordering list, then use that.
273	# It works by just using pathname expansion in the interface directory.
274	if [ -n "$1" ]; then
275		list="$*"
276		$force || report=true
277	elif ! $excl; then
278		cd "$IFACEDIR"
279		for i in $interface_order; do
280			[ -f "$i" ] && list="$list $i"
281			for ii in "$i":* "$i".*; do
282				[ -f "$ii" ] && list="$list $ii"
283			done
284		done
285		for i in $dynamic_order; do
286			if [ -e "$i" -a ! -e "$METRICDIR/"*" $i" ]; then
287				list="$list $i"
288			fi
289			for ii in "$i":* "$i".*; do
290				if [ -f "$ii" -a ! -e "$METRICDIR/"*" $ii" ]; then
291					list="$list $ii"
292				fi
293			done
294		done
295		if [ -d "$METRICDIR" ]; then
296			cd "$METRICDIR"
297			for i in *; do
298				[ -f "$i" ] && list="$list ${i#* }"
299			done
300		fi
301		list="$list *"
302	fi
303
304	cd "$IFACEDIR"
305	retval=1
306	for i in $(uniqify $list); do
307		# Only list interfaces which we really have
308		if ! [ -f "$i" ]; then
309			if $report; then
310				echo "No resolv.conf for interface $i" >&2
311				retval=2
312			fi
313			continue
314		fi
315
316		if [ "$cmd" = i -o "$cmd" = "-i" ]; then
317			printf %s "$i "
318		else
319			echo_resolv "$i"
320		fi
321		[ $? = 0 -a "$retval" = 1 ] && retval=0
322	done
323	[ "$cmd" = i -o "$cmd" = "-i" ] && echo
324	return $retval
325}
326
327list_remove() {
328	local list= e= l= result= found= retval=0
329
330	[ -z "$2" ] && return 0
331	eval list=\"\$$1\"
332	shift
333
334	set -f
335	for e; do
336		found=false
337		for l in $list; do
338			case "$e" in
339			$l) found=true;;
340			esac
341			$found && break
342		done
343		if $found; then
344			retval=$(($retval + 1))
345		else
346			result="$result $e"
347		fi
348	done
349	set +f
350	echo "${result# *}"
351	return $retval
352}
353
354echo_prepend()
355{
356	echo "# Generated by resolvconf"
357	if [ -n "$search_domains" ]; then
358		echo "search $search_domains"
359	fi
360	for n in $name_servers; do
361		echo "nameserver $n"
362	done
363	echo
364}
365
366echo_append()
367{
368	echo "# Generated by resolvconf"
369	if [ -n "$search_domains_append" ]; then
370		echo "search $search_domains_append"
371	fi
372	for n in $name_servers_append; do
373		echo "nameserver $n"
374	done
375	echo
376}
377
378replace()
379{
380	local r= k= f= v= val= sub=
381
382	while read -r keyword value; do
383		for r in $replace; do
384			k="${r%%/*}"
385			r="${r#*/}"
386			f="${r%%/*}"
387			r="${r#*/}"
388			v="${r%%/*}"
389			case "$keyword" in
390			$k)
391				case "$value" in
392				$f) value="$v";;
393				esac
394				;;
395			esac
396		done
397		val=
398		for sub in $value; do
399			for r in $replace_sub; do
400				k="${r%%/*}"
401				r="${r#*/}"
402				f="${r%%/*}"
403				r="${r#*/}"
404				v="${r%%/*}"
405				case "$keyword" in
406				$k)
407					case "$sub" in
408					$f) sub="$v";;
409					esac
410					;;
411				esac
412			done
413			val="$val${val:+ }$sub"
414		done
415		printf "%s %s\n" "$keyword" "$val"
416	done
417}
418
419make_vars()
420{
421	local newdomains= d= dn= newns= ns=
422
423	# Clear variables
424	DOMAIN=
425	DOMAINS=
426	SEARCH=
427	NAMESERVERS=
428	LOCALNAMESERVERS=
429
430	if [ -n "$name_servers" -o -n "$search_domains" ]; then
431		eval "$(echo_prepend | parse_resolv)"
432	fi
433	if [ -z "$VFLAG" ]; then
434		IF_EXCLUSIVE=1
435		list_resolv -i "$@" >/dev/null || IF_EXCLUSIVE=0
436		eval "$(list_resolv -l "$@" | replace | parse_resolv)"
437	fi
438	if [ -n "$name_servers_append" -o -n "$search_domains_append" ]; then
439		eval "$(echo_append | parse_resolv)"
440	fi
441
442	# Ensure that we only list each domain once
443	for d in $DOMAINS; do
444		dn="${d%%:*}"
445		list_remove domain_blacklist "$dn" >/dev/null || continue
446		case " $newdomains" in
447		*" ${dn}:"*) continue;;
448		esac
449		newns=
450		for nd in $DOMAINS; do
451			if [ "$dn" = "${nd%%:*}" ]; then
452				ns="${nd#*:}"
453				while [ -n "$ns" ]; do
454					case ",$newns," in
455					*,${ns%%,*},*) ;;
456					*) list_remove name_server_blacklist \
457						"${ns%%,*}" >/dev/null \
458					&& newns="$newns${newns:+,}${ns%%,*}";;
459					esac
460					[ "$ns" = "${ns#*,}" ] && break
461					ns="${ns#*,}"
462				done
463			fi
464		done
465		if [ -n "$newns" ]; then
466			newdomains="$newdomains${newdomains:+ }$dn:$newns"
467		fi
468	done
469	DOMAIN="$(list_remove domain_blacklist $DOMAIN)"
470	SEARCH="$(uniqify $SEARCH)"
471	SEARCH="$(list_remove domain_blacklist $SEARCH)"
472	NAMESERVERS="$(uniqify $NAMESERVERS)"
473	NAMESERVERS="$(list_remove name_server_blacklist $NAMESERVERS)"
474	LOCALNAMESERVERS="$(uniqify $LOCALNAMESERVERS)"
475	LOCALNAMESERVERS="$(list_remove name_server_blacklist $LOCALNAMESERVERS)"
476	echo "DOMAIN='$DOMAIN'"
477	echo "SEARCH='$SEARCH'"
478	echo "NAMESERVERS='$NAMESERVERS'"
479	echo "LOCALNAMESERVERS='$LOCALNAMESERVERS'"
480	echo "DOMAINS='$newdomains'"
481}
482
483force=false
484VFLAG=
485while getopts a:Dd:fhIilm:puvVx OPT; do
486	case "$OPT" in
487	f) force=true;;
488	h) usage;;
489	m) IF_METRIC="$OPTARG";;
490	p) IF_PRIVATE=1;;
491	V)
492		VFLAG=1
493		if [ "$local_nameservers" = \
494		    "127.* 0.0.0.0 255.255.255.255 ::1" ]
495		then
496			local_nameservers=
497		fi
498		;;
499	x) IF_EXCLUSIVE=1;;
500	'?') ;;
501	*) cmd="$OPT"; iface="$OPTARG";;
502	esac
503done
504shift $(($OPTIND - 1))
505args="$iface${iface:+ }$*"
506
507# -I inits the state dir
508if [ "$cmd" = I ]; then
509	if [ -d "$VARDIR" ]; then
510		rm -rf "$VARDIR"/*
511	fi
512	exit $?
513fi
514
515# -D ensures that the listed config file base dirs exist
516if [ "$cmd" = D ]; then
517	config_mkdirs "$@"
518	exit $?
519fi
520
521# -l lists our resolv files, optionally for a specific interface
522if [ "$cmd" = l -o "$cmd" = i ]; then
523	list_resolv "$cmd" "$args"
524	exit $?
525fi
526
527# Not normally needed, but subscribers should be able to run independently
528if [ "$cmd" = v -o -n "$VFLAG" ]; then
529	make_vars "$iface"
530	exit $?
531fi
532
533# Test that we have valid options
534if [ "$cmd" = a -o "$cmd" = d ]; then
535	if [ -z "$iface" ]; then
536		usage "Interface not specified"
537	fi
538elif [ "$cmd" != u ]; then
539	[ -n "$cmd" -a "$cmd" != h ] && usage "Unknown option $cmd"
540	usage
541fi
542
543if [ "$cmd" = a ]; then
544	for x in '/' \\ ' ' '*'; do
545		case "$iface" in
546		*[$x]*) error_exit "$x not allowed in interface name";;
547		esac
548	done
549	for x in '.' '-' '~'; do
550		case "$iface" in
551		[$x]*) error_exit \
552			"$x not allowed at start of interface name";;
553		esac
554	done
555	[ "$cmd" = a -a -t 0 ] && error_exit "No file given via stdin"
556fi
557
558if [ ! -d "$VARDIR" ]; then
559	if [ -L "$VARDIR" ]; then
560		dir="$(readlink "$VARDIR")"
561		# link maybe relative
562		cd "${VARDIR%/*}"
563		if ! mkdir -m 0755 -p "$dir"; then
564			error_exit "Failed to create needed" \
565				"directory $dir"
566		fi
567	else
568		if ! mkdir -m 0755 -p "$VARDIR"; then
569			error_exit "Failed to create needed" \
570				"directory $VARDIR"
571		fi
572	fi
573fi
574
575if [ ! -d "$IFACEDIR" ]; then
576	mkdir -m 0755 -p "$IFACEDIR" || \
577		error_exit "Failed to create needed directory $IFACEDIR"
578	if [ "$cmd" = d ]; then
579		# Provide the same error messages as below
580		if ! ${force}; then
581			cd "$IFACEDIR"
582			for i in $args; do
583				warn "No resolv.conf for interface $i"
584			done
585		fi
586		${force}
587		exit $?
588	fi
589fi
590
591# An interface was added, changed, deleted or a general update was called.
592# Due to exclusivity we need to ensure that this is an atomic operation.
593# Our subscribers *may* need this as well if the init system is sub par.
594# As such we spinlock at this point as best we can.
595# We don't use flock(1) because it's not widely available and normally resides
596# in /usr which we do our very best to operate without.
597[ -w "$VARDIR" ] || error_exit "Cannot write to $LOCKDIR"
598: ${lock_timeout:=10}
599while true; do
600	if mkdir "$LOCKDIR" 2>/dev/null; then
601		trap 'rm -rf "$LOCKDIR";' EXIT
602		trap 'rm -rf "$LOCKDIR"; exit 1' INT QUIT ABRT SEGV ALRM TERM
603		echo $$ >"$LOCKDIR/pid"
604		break
605	fi
606	pid=$(cat "$LOCKDIR/pid")
607	if ! kill -0 "$pid"; then
608		warn "clearing stale lock pid $pid"
609		rm -rf "$LOCKDIR"
610		continue
611	fi
612	lock_timeout=$(($lock_timeout - 1))
613	if [ "$lock_timeout" -le 0 ]; then
614		error_exit "timed out waiting for lock from pid $pid"
615	fi
616	sleep 1
617done
618
619case "$cmd" in
620a)
621	# Read resolv.conf from stdin
622	resolv="$(cat)"
623	changed=false
624	changedfile=false
625	# If what we are given matches what we have, then do nothing
626	if [ -e "$IFACEDIR/$iface" ]; then
627		if [ "$(echo "$resolv")" != \
628			"$(cat "$IFACEDIR/$iface")" ]
629		then
630			changed=true
631			changedfile=true
632		fi
633	else
634		changed=true
635		changedfile=true
636	fi
637
638	# Set metric and private before creating the interface resolv.conf file
639	# to ensure that it will have the correct flags
640	[ ! -d "$METRICDIR" ] && mkdir "$METRICDIR"
641	oldmetric="$METRICDIR/"*" $iface"
642	newmetric=
643	if [ -n "$IF_METRIC" ]; then
644		# Pad metric to 6 characters, so 5 is less than 10
645		while [ ${#IF_METRIC} -le 6 ]; do
646			IF_METRIC="0$IF_METRIC"
647		done
648		newmetric="$METRICDIR/$IF_METRIC $iface"
649	fi
650	rm -f "$METRICDIR/"*" $iface"
651	[ "$oldmetric" != "$newmetric" -a \
652	    "$oldmetric" != "$METRICDIR/* $iface" ] &&
653		changed=true
654	[ -n "$newmetric" ] && echo " " >"$newmetric"
655
656	case "$IF_PRIVATE" in
657	[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
658		if [ ! -d "$PRIVATEDIR" ]; then
659			[ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR"
660			mkdir "$PRIVATEDIR"
661		fi
662		[ -e "$PRIVATEDIR/$iface" ] || changed=true
663		[ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface"
664		;;
665	*)
666		if [ -e "$PRIVATEDIR/$iface" ]; then
667			rm -f "$PRIVATEDIR/$iface"
668			changed=true
669		fi
670		;;
671	esac
672
673	oldexcl=
674	for x in "$EXCLUSIVEDIR/"*" $iface"; do
675		if [ -f "$x" ]; then
676			oldexcl="$x"
677			break
678		fi
679	done
680	case "$IF_EXCLUSIVE" in
681	[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
682		if [ ! -d "$EXCLUSIVEDIR" ]; then
683			[ -e "$EXCLUSIVEDIR" ] && rm "$EXCLUSIVEDIR"
684			mkdir "$EXCLUSIVEDIR"
685		fi
686		cd "$EXCLUSIVEDIR"
687		for x in *; do
688			[ -f "$x" ] && break
689		done
690		if [ "${x#* }" != "$iface" ]; then
691			if [ "$x" = "${x% *}" ]; then
692				x=10000000
693			else
694				x="${x% *}"
695			fi
696			if [ "$x" = "0000000" ]; then
697				warn "exclusive underflow"
698			else
699				x=$(($x - 1))
700			fi
701			if [ -d "$EXCLUSIVEDIR" ]; then
702				echo " " >"$EXCLUSIVEDIR/$x $iface"
703			fi
704			changed=true
705		fi
706		;;
707	*)
708		if [ -f "$oldexcl" ]; then
709			rm -f "$oldexcl"
710			changed=true
711		fi
712		;;
713	esac
714
715	if $changedfile; then
716		printf "%s\n" "$resolv" >"$IFACEDIR/$iface" || exit $?
717	elif ! $changed; then
718		exit 0
719	fi
720	unset changed changedfile oldmetric newmetric x oldexcl
721	;;
722
723d)
724	# Delete any existing information about the interface
725	cd "$IFACEDIR"
726	changed=false
727	for i in $args; do
728		if [ -e "$i" ]; then
729			changed=true
730		elif ! ${force}; then
731			warn "No resolv.conf for interface $i"
732		fi
733		rm -f "$i" "$METRICDIR/"*" $i" \
734			"$PRIVATEDIR/$i" \
735			"$EXCLUSIVEDIR/"*" $i" || exit $?
736	done
737	if ! ${changed}; then
738		# Set the return code based on the forced flag
739		${force}
740		exit $?
741	fi
742	unset changed i
743	;;
744esac
745
746case "${resolvconf:-YES}" in
747[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
748*) exit 0;;
749esac
750
751eval "$(make_vars)"
752export RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS
753: ${list_resolv:=list_resolv -l}
754retval=0
755for script in "$LIBEXECDIR"/*; do
756	if [ -f "$script" ]; then
757		eval script_enabled="\$${script##*/}"
758		case "${script_enabled:-YES}" in
759		[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
760		*) continue;;
761		esac
762		if [ -x "$script" ]; then
763			"$script" "$cmd" "$iface"
764		else
765			(set -- "$cmd" "$iface"; . "$script")
766		fi
767		retval=$(($retval + $?))
768	fi
769done
770exit $retval
771