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