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