1#!/bin/sh 2# Copyright (c) 2007-2025 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" 28OPENRESOLV_VERSION="3.14.0" 29SYSCONFDIR=@SYSCONFDIR@ 30LIBEXECDIR=@LIBEXECDIR@ 31VARDIR=@VARDIR@ 32RCDIR=@RCDIR@ 33RESTARTCMD=@RESTARTCMD@ 34 35if [ "$1" = "--version" ]; then 36 echo "openresolv $OPENRESOLV_VERSION" 37 echo "Copyright (c) 2007-2025 Roy Marples" 38 exit 0 39fi 40 41# Disregard dhcpcd setting 42unset interface_order state_dir 43 44# If you change this, change the test in VFLAG and libc.in as well 45local_nameservers="127.* 0.0.0.0 255.255.255.255 ::1" 46 47dynamic_order="tap[0-9]* tun[0-9]* vpn vpn[0-9]* wg[0-9]* ppp[0-9]* ippp[0-9]*" 48interface_order="lo lo[0-9]*" 49name_server_blacklist="0.0.0.0" 50 51# Poor mans cat 52# /usr might not be available 53cat() 54{ 55 OIFS="$IFS" 56 IFS='' 57 if [ -n "$1" ]; then 58 while read -r line; do 59 printf "%s\n" "$line" 60 done < "$1" 61 else 62 while read -r line; do 63 printf "%s\n" "$line" 64 done 65 fi 66 retval=$? 67 IFS="$OIFS" 68 return $retval 69} 70 71 72# Support original resolvconf configuration layout 73# as well as the openresolv config file 74if [ -f "$SYSCONFDIR"/resolvconf.conf ]; then 75 . "$SYSCONFDIR"/resolvconf.conf 76 [ -n "$state_dir" ] && VARDIR="$state_dir" 77elif [ -d "$SYSCONFDIR/resolvconf" ]; then 78 SYSCONFDIR="$SYSCONFDIR/resolvconf" 79 if [ -f "$SYSCONFDIR"/interface-order ]; then 80 interface_order="$(cat "$SYSCONFDIR"/interface-order)" 81 fi 82fi 83 84IFACEDIR="$VARDIR/interfaces" 85METRICDIR="$VARDIR/metrics" 86PRIVATEDIR="$VARDIR/private" 87EXCLUSIVEDIR="$VARDIR/exclusive" 88DEPRECATEDDIR="$VARDIR/deprecated" 89LOCKDIR="$VARDIR/lock" 90_PWD="$PWD" 91 92warn() 93{ 94 echo "$*" >&2 95} 96 97error_exit() 98{ 99 echo "$*" >&2 100 exit 1 101} 102 103usage() 104{ 105 cat <<-EOF 106 Usage: ${RESOLVCONF##*/} [options] command [argument] 107 108 Inform the system about any DNS updates. 109 110 Commands: 111 -a \$INTERFACE Add DNS information to the specified interface 112 (DNS supplied via stdin in resolv.conf format) 113 -C \$PATTERN Deprecate DNS information for matched interfaces 114 -c \$PATTERN Configure DNS information for matched interfaces 115 -d \$INTERFACE Delete DNS information from the specified interface 116 -h Show this help cruft 117 -i [\$PATTERN] Show interfaces that have supplied DNS information 118 optionally from interfaces that match the specified 119 pattern 120 -l [\$PATTERN] Show DNS information, optionally from interfaces 121 that match the specified pattern 122 -L [\$PATTERN] Same as -l, but adjusted by our config 123 124 -u Run updates from our current DNS information 125 --version Echo the ${RESOLVCONF##*/} version 126 127 Options: 128 -f Ignore non existent interfaces 129 -m metric Give the added DNS information a metric 130 -p Mark the interface as private 131 -x Mark the interface as exclusive 132 133 Subscriber and System Init Commands: 134 -I Init the state dir 135 -r \$SERVICE Restart the system service 136 (restarting a non-existent or non-running service 137 should have no output and return 0) 138 -R Show the system service restart command 139 -v [\$PATTERN] echo NEWDOMAIN, NEWSEARCH and NEWNS variables to 140 the console 141 -V [\$PATTERN] Same as -v, but only uses configuration in 142 $SYSCONFDIR/resolvconf.conf 143 EOF 144 [ -z "$1" ] && exit 0 145 echo 146 error_exit "$*" 147} 148 149# Strip any trailing dot from each name as a FQDN does not belong 150# in resolv.conf(5) 151# If you think otherwise, capture a DNS trace and you'll see libc 152# will strip it regardless. 153# This also solves setting up duplicate zones in our subscribers. 154# Also strip any comments denoted by #. 155resolv_strip() 156{ 157 space= 158 for word; do 159 case "$word" in 160 \#*) break;; 161 esac 162 printf "%s%s" "$space${word%.}" 163 space=" " 164 done 165 printf "\n" 166} 167 168private_iface() 169{ 170 # Allow expansion 171 cd "$IFACEDIR" 172 173 # Public interfaces override private ones. 174 for p in $public_interfaces; do 175 case "$iface" in 176 "$p"|"$p":*) return 1;; 177 esac 178 done 179 180 if [ -e "$PRIVATEDIR/$iface" ]; then 181 return 0 182 fi 183 184 for p in $private_interfaces; do 185 case "$iface" in 186 "$p"|"$p":*) return 0;; 187 esac 188 done 189 190 # Not a private interface 191 return 1 192} 193 194# Parse resolv.conf's and make variables 195# for domain name servers, search name servers and global nameservers 196parse_resolv() 197{ 198 domain= 199 new=true 200 newns= 201 ns= 202 private=false 203 search= 204 205 while read -r line; do 206 stripped_line="$(resolv_strip ${line#* })" 207 case "$line" in 208 "# resolv.conf from "*) 209 if ${new}; then 210 iface="${line#\# resolv.conf from *}" 211 new=false 212 if private_iface "$iface"; then 213 private=true 214 else 215 private=false 216 fi 217 fi 218 ;; 219 "nameserver "*) 220 islocal=false 221 for l in $local_nameservers; do 222 case "$stripped_line" in 223 $l) 224 islocal=true 225 break 226 ;; 227 esac 228 done 229 if $islocal; then 230 echo "LOCALNAMESERVERS=\"\$LOCALNAMESERVERS $stripped_line\"" 231 else 232 ns="$ns$stripped_line " 233 fi 234 ;; 235 "domain "*) 236 search="$stripped_line" 237 if [ -z "$domain" ]; then 238 domain="$search" 239 echo "DOMAIN=\"$domain\"" 240 fi 241 ;; 242 "search "*) 243 search="$stripped_line" 244 ;; 245 *) 246 [ -n "$line" ] && continue 247 if [ -n "$ns" ] && [ -n "$search" ]; then 248 newns= 249 for n in $ns; do 250 newns="$newns${newns:+,}$n" 251 done 252 ds= 253 for d in $search; do 254 ds="$ds${ds:+ }$d:$newns" 255 done 256 echo "DOMAINS=\"\$DOMAINS $ds\"" 257 fi 258 echo "SEARCH=\"\$SEARCH $search\"" 259 if ! $private; then 260 echo "NAMESERVERS=\"\$NAMESERVERS $ns\"" 261 fi 262 ns= 263 search= 264 new=true 265 ;; 266 esac 267 done 268} 269 270uniqify() 271{ 272 result= 273 while [ -n "$1" ]; do 274 case " $result " in 275 *" $1 "*);; 276 *) result="$result $1";; 277 esac 278 shift 279 done 280 echo "${result# *}" 281} 282 283dirname() 284{ 285 OIFS="$IFS" 286 IFS=/ 287 set -- $@ 288 IFS="$OIFS" 289 if [ -n "$1" ]; then 290 printf %s . 291 else 292 shift 293 fi 294 while [ -n "$2" ]; do 295 printf "/%s" "$1" 296 shift 297 done 298 printf "\n" 299} 300 301config_mkdirs() 302{ 303 for f; do 304 [ -n "$f" ] || continue 305 d="$(dirname "$f")" 306 if [ ! -d "$d" ]; then 307 mkdir -p "$d" || return $? 308 fi 309 done 310 return 0 311} 312 313# With the advent of alternative init systems, it's possible to have 314# more than one installed. So we need to try and guess what one we're 315# using unless overridden by configure. 316# Note that restarting a service is a last resort - the subscribers 317# should make a reasonable attempt to reconfigure the service via some 318# method, normally SIGHUP. 319detect_init() 320{ 321 [ -n "$RESTARTCMD" ] && return 0 322 323 # Detect the running init system. 324 # As systemd and OpenRC can be installed on top of legacy init 325 # systems we try to detect them first. 326 status="@STATUSARG@" 327 : ${status:=status} 328 if [ -x /bin/systemctl ] && [ -S /run/systemd/private ]; then 329 RESTARTCMD=' 330 if /bin/systemctl --quiet is-active $1.service 331 then 332 /bin/systemctl restart $1.service 333 fi' 334 elif [ -x /usr/bin/systemctl ] && [ -S /run/systemd/private ]; then 335 RESTARTCMD=' 336 if /usr/bin/systemctl --quiet is-active $1.service 337 then 338 /usr/bin/systemctl restart $1.service 339 fi' 340 elif [ -x /sbin/rc-service ] && 341 { [ -s /libexec/rc/init.d/softlevel ] || 342 [ -s /run/openrc/softlevel ]; } 343 then 344 RESTARTCMD='/sbin/rc-service -i $1 -- -Ds restart' 345 elif [ -x /usr/sbin/invoke-rc.d ]; then 346 RCDIR=/etc/init.d 347 RESTARTCMD=' 348 if /usr/sbin/invoke-rc.d --quiet $1 status >/dev/null 2>&1 349 then 350 /usr/sbin/invoke-rc.d $1 restart 351 fi' 352 elif [ -x /usr/bin/s6-rc ] && [ -x /usr/bin/s6-svc ]; then 353 RESTARTCMD=' 354 if s6-rc -a list 2>/dev/null | grep -qFx $1-srv 355 then 356 s6-svc -r /run/service/$1-srv 357 fi' 358 elif [ -x /sbin/service ]; then 359 # Old RedHat 360 RCDIR=/etc/init.d 361 RESTARTCMD=' 362 if /sbin/service $1; then 363 /sbin/service $1 restart 364 fi' 365 elif [ -x /usr/sbin/service ]; then 366 # Could be FreeBSD 367 RESTARTCMD=" 368 if /usr/sbin/service \$1 $status >/dev/null 2>&1 369 then 370 /usr/sbin/service \$1 restart 371 fi" 372 elif [ -x /bin/sv ]; then 373 RESTARTCMD='/bin/sv status $1 >/dev/null 2>&1 && 374 /bin/sv try-restart $1' 375 elif [ -x /usr/bin/sv ]; then 376 RESTARTCMD='/usr/bin/sv status $1 >/dev/null 2>&1 && 377 /usr/bin/sv try-restart $1' 378 elif [ -e /etc/arch-release ] && [ -d /etc/rc.d ]; then 379 RCDIR=/etc/rc.d 380 RESTARTCMD=' 381 if [ -e /var/run/daemons/$1 ] 382 then 383 /etc/rc.d/$1 restart 384 fi' 385 elif [ -e /etc/slackware-version ] && [ -d /etc/rc.d ]; then 386 RESTARTCMD=' 387 if /etc/rc.d/rc.$1 status >/dev/null 2>&1 388 then 389 /etc/rc.d/rc.$1 restart 390 fi' 391 elif [ -e /etc/rc.d/rc.subr ] && [ -d /etc/rc.d ]; then 392 # OpenBSD 393 RESTARTCMD=' 394 if /etc/rc.d/$1 check >/dev/null 2>&1 395 then 396 /etc/rc.d/$1 restart 397 fi' 398 elif [ -d /etc/dinit.d ] && command -v dinitctl >/dev/null 2>&1; then 399 RESTARTCMD='dinitctl --quiet restart --ignore-unstarted $1' 400 else 401 for x in /etc/init.d/rc.d /etc/rc.d /etc/init.d; do 402 [ -d $x ] || continue 403 RESTARTCMD=" 404 if $x/\$1 $status >/dev/null 2>&1 405 then 406 $x/\$1 restart 407 fi" 408 break 409 done 410 fi 411 412 if [ -z "$RESTARTCMD" ]; then 413 if [ "$_NOINIT_WARNED" != true ]; then 414 warn "could not detect a useable init system" 415 _NOINIT_WARNED=true 416 fi 417 return 1 418 fi 419 _NOINIT_WARNED= 420 return 0 421} 422 423echo_resolv() 424{ 425 OIFS="$IFS" 426 427 [ -n "$1" ] && [ -f "$IFACEDIR/$1" ] || return 1 428 echo "# resolv.conf from $1" 429 # Our variable maker works of the fact each resolv.conf per interface 430 # is separated by blank lines. 431 # So we remove them when echoing them. 432 while read -r line; do 433 IFS="$OIFS" 434 if [ -n "$line" ]; then 435 # We need to set IFS here to preserve any whitespace 436 IFS='' 437 printf "%s\n" "$line" 438 fi 439 done < "$IFACEDIR/$1" 440 IFS="$OIFS" 441} 442 443deprecated_interface() 444{ 445 [ -d "$DEPRECATEDDIR" ] || return 1 446 447 cd "$DEPRECATEDDIR" 448 for da; do 449 for daf in *; do 450 [ -f "$daf" ] || continue 451 case "$da" in 452 $daf) return 0;; 453 esac 454 done 455 done 456 return 1 457} 458 459match() 460{ 461 match="$1" 462 file="$2" 463 retval=1 464 count=0 465 466 while read -r keyword value; do 467 new_match= 468 for om in $match; do 469 m="$om" 470 keep= 471 while [ -n "$m" ]; do 472 k="${m%%/*}" 473 r="${m#*/}" 474 f="${r%%/*}" 475 r="${r#*/}" 476 # If the length of m is the same as k/f then 477 # we know that we are done 478 if [ ${#m} = $((${#k} + 1 + ${#f})) ]; then 479 r= 480 fi 481 m="$r" 482 matched=false 483 case "$keyword" in 484 $k) 485 case "$value" in 486 $f) 487 matched=true 488 ;; 489 esac 490 ;; 491 esac 492 if ! $matched; then 493 keep="$keep${keep:+/}$k/$f" 494 fi 495 done 496 if [ -n "$om" ] && [ -z "$keep" ]; then 497 retval=0 498 break 2 499 fi 500 new_match="${new_match}${new_match:+ }${keep}" 501 done 502 match="${new_match}" 503 done < "$file" 504 return $retval 505} 506 507list_resolv() 508{ 509 [ -d "$IFACEDIR" ] || return 0 510 511 OPTIND= 512 list_cmd= 513 while getopts iLl OPT; do 514 case "$OPT" in 515 '?') exit 1;; 516 *) list_cmd="$OPT";; 517 esac 518 done 519 shift $(($OPTIND - 1)) 520 521 pattern_specified="$1" 522 523 excl=false 524 list= 525 report=false 526 retval=0 527 528 case "$IF_EXCLUSIVE" in 529 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) 530 excl=true 531 if [ -d "$EXCLUSIVEDIR" ]; then 532 cd "$EXCLUSIVEDIR" 533 for i in *; do 534 if [ -f "$i" ]; then 535 list="${i#* }" 536 break 537 fi 538 done 539 fi 540 cd "$IFACEDIR" 541 for i in $inclusive_interfaces; do 542 if [ -f "$i" ] && [ "$list" = "$i" ]; then 543 list= 544 excl=false 545 break 546 fi 547 done 548 ;; 549 esac 550 551 # If we have an interface ordering list, then use that. 552 # It works by just using pathname expansion in the interface directory. 553 if [ -n "$pattern_specified" ]; then 554 list="$*" 555 $force || report=true 556 elif ! $excl; then 557 cd "$IFACEDIR" 558 559 for i in $interface_order; do 560 [ -f "$i" ] && list="$list $i" 561 for ii in "$i":* "$i".*; do 562 [ -f "$ii" ] && list="$list $ii" 563 done 564 done 565 566 for i in $dynamic_order; do 567 if [ -e "$i" ] && ! [ -e "$METRICDIR/"*" $i" ]; then 568 list="$list $i" 569 fi 570 for ii in "$i":* "$i".*; do 571 if [ -f "$ii" ] && ! [ -e "$METRICDIR/"*" $ii" ] 572 then 573 list="$list $ii" 574 fi 575 done 576 done 577 578 # Interfaces have an implicit metric of 0 if not specified. 579 for i in *; do 580 if [ -f "$i" ] && ! [ -e "$METRICDIR/"*" $i" ]; then 581 list="$list $i" 582 fi 583 done 584 585 if [ -d "$METRICDIR" ]; then 586 cd "$METRICDIR" 587 for i in *; do 588 [ -f "$i" ] && list="$list ${i#* }" 589 done 590 fi 591 592 # Move deprecated interfaces to the back 593 active= 594 deprecated= 595 for i in $list; do 596 if deprecated_interface "$i"; then 597 deprecated="$deprecated $i" 598 else 599 active="$active $i" 600 fi 601 done 602 list="$active $deprecated" 603 fi 604 605 cd "$IFACEDIR" 606 if $excl || [ -n "$pattern_specified" ]; then 607 retval=1 608 else 609 retval=0 610 fi 611 for i in $(uniqify $list); do 612 # Only list interfaces which we really have 613 if ! [ -f "$i" ]; then 614 if $report; then 615 echo "No resolv.conf for interface $i" >&2 616 retval=2 617 fi 618 continue 619 fi 620 621 if [ "$list_cmd" = L ]; then 622 if [ -n "$allow_interfaces" ]; then 623 x=false 624 for j in $allow_interfaces; do 625 if [ "$i" = "$j" ]; then 626 x=true 627 fi 628 done 629 $x || continue 630 fi 631 for j in $deny_interfaces; do 632 if [ "$i" = "$j" ]; then 633 continue 2 634 fi 635 done 636 637 if [ -n "$exclude" ] && match "$exclude" "$i"; then 638 continue 639 fi 640 fi 641 642 if [ "$list_cmd" = i ]; then 643 printf %s "$i " 644 else 645 echo_resolv "$i" && echo 646 fi 647 [ $? = 0 ] && [ "$retval" = 1 ] && retval=0 648 done 649 [ "$list_cmd" = i ] && echo 650 return $retval 651} 652 653list_remove() 654{ 655 [ -z "$2" ] && return 0 656 eval list=\"\$$1\" 657 shift 658 result= 659 retval=0 660 661 set -f 662 for e; do 663 found=false 664 for l in $list; do 665 case "$e" in 666 $l) found=true;; 667 esac 668 $found && break 669 done 670 if $found; then 671 retval=$(($retval + 1)) 672 else 673 result="$result $e" 674 fi 675 done 676 set +f 677 echo "${result# *}" 678 return $retval 679} 680 681echo_prepend() 682{ 683 echo "# Generated by resolvconf" 684 if [ -n "$search_domains" ]; then 685 echo "search $search_domains" 686 fi 687 for n in $name_servers; do 688 echo "nameserver $n" 689 done 690 echo 691} 692 693echo_append() 694{ 695 echo "# Generated by resolvconf" 696 if [ -n "$search_domains_append" ]; then 697 echo "search $search_domains_append" 698 fi 699 for n in $name_servers_append; do 700 echo "nameserver $n" 701 done 702 echo 703} 704 705replace() 706{ 707 while read -r keyword value; do 708 for r in $replace; do 709 k="${r%%/*}" 710 r="${r#*/}" 711 f="${r%%/*}" 712 r="${r#*/}" 713 v="${r%%/*}" 714 case "$keyword" in 715 $k) 716 case "$value" in 717 $f) value="$v";; 718 esac 719 ;; 720 esac 721 done 722 val= 723 for sub in $value; do 724 for r in $replace_sub; do 725 k="${r%%/*}" 726 r="${r#*/}" 727 f="${r%%/*}" 728 r="${r#*/}" 729 v="${r%%/*}" 730 case "$keyword" in 731 $k) 732 case "$sub" in 733 $f) sub="$v";; 734 esac 735 ;; 736 esac 737 done 738 val="$val${val:+ }$sub" 739 done 740 printf "%s %s\n" "$keyword" "$val" 741 done 742} 743 744make_vars() 745{ 746 # Clear variables 747 DOMAIN= 748 DOMAINS= 749 SEARCH= 750 NAMESERVERS= 751 LOCALNAMESERVERS= 752 753 if [ -n "${name_servers}${search_domains}" ]; then 754 eval "$(echo_prepend | parse_resolv)" 755 fi 756 if [ -z "$VFLAG" ]; then 757 IF_EXCLUSIVE=1 758 list_resolv -i "$@" >/dev/null || IF_EXCLUSIVE=0 759 eval "$(list_resolv -L "$@" | replace | parse_resolv)" 760 fi 761 if [ -n "${name_servers_append}${search_domains_append}" ]; then 762 eval "$(echo_append | parse_resolv)" 763 fi 764 765 # Ensure that we only list each domain once 766 newdomains= 767 for d in $DOMAINS; do 768 dn="${d%%:*}" 769 list_remove domain_blacklist "$dn" >/dev/null || continue 770 case " $newdomains" in 771 *" ${dn}:"*) continue;; 772 esac 773 newns= 774 for nd in $DOMAINS; do 775 if [ "$dn" = "${nd%%:*}" ]; then 776 ns="${nd#*:}" 777 while [ -n "$ns" ]; do 778 case ",$newns," in 779 *,${ns%%,*},*) ;; 780 *) list_remove name_server_blacklist \ 781 "${ns%%,*}" >/dev/null \ 782 && newns="$newns${newns:+,}${ns%%,*}";; 783 esac 784 [ "$ns" = "${ns#*,}" ] && break 785 ns="${ns#*,}" 786 done 787 fi 788 done 789 if [ -n "$newns" ]; then 790 newdomains="$newdomains${newdomains:+ }$dn:$newns" 791 fi 792 done 793 DOMAIN="$(list_remove domain_blacklist $DOMAIN)" 794 SEARCH="$(uniqify $SEARCH)" 795 SEARCH="$(list_remove domain_blacklist $SEARCH)" 796 NAMESERVERS="$(uniqify $NAMESERVERS)" 797 NAMESERVERS="$(list_remove name_server_blacklist $NAMESERVERS)" 798 LOCALNAMESERVERS="$(uniqify $LOCALNAMESERVERS)" 799 LOCALNAMESERVERS="$(list_remove name_server_blacklist $LOCALNAMESERVERS)" 800 echo "DOMAIN='$DOMAIN'" 801 echo "SEARCH='$SEARCH'" 802 echo "NAMESERVERS='$NAMESERVERS'" 803 echo "LOCALNAMESERVERS='$LOCALNAMESERVERS'" 804 echo "DOMAINS='$newdomains'" 805} 806 807force=false 808VFLAG= 809while getopts a:C:c:Dd:fhIiLlm:pRruvVx OPT; do 810 case "$OPT" in 811 f) force=true;; 812 h) usage;; 813 m) IF_METRIC="$OPTARG";; 814 p) IF_PRIVATE=1;; 815 V) 816 VFLAG=1 817 if [ "$local_nameservers" = \ 818 "127.* 0.0.0.0 255.255.255.255 ::1" ] 819 then 820 local_nameservers= 821 fi 822 ;; 823 x) IF_EXCLUSIVE=1;; 824 '?') exit 1;; 825 *) cmd="$OPT"; iface="$OPTARG";; 826 esac 827done 828shift $(($OPTIND - 1)) 829args="$iface${iface:+ }$*" 830 831# -I inits the state dir 832if [ "$cmd" = I ]; then 833 if [ -d "$VARDIR" ]; then 834 rm -rf "$VARDIR"/* 835 fi 836 exit $? 837fi 838 839# -D ensures that the listed config file base dirs exist 840if [ "$cmd" = D ]; then 841 config_mkdirs "$@" 842 exit $? 843fi 844 845# -l lists our resolv files, optionally for a specific interface 846if [ "$cmd" = l ] || [ "$cmd" = i ]; then 847 list_resolv "-$cmd" "$args" 848 exit $? 849fi 850# -L is the same as -l, but post-processed from our config 851if [ "$cmd" = L ]; then 852 list_resolv "-$cmd" "$args" | replace 853 exit $?2 854fi 855 856# Restart a service or echo the command to restart a service 857if [ "$cmd" = r ] || [ "$cmd" = R ]; then 858 detect_init || exit 1 859 if [ "$cmd" = r ]; then 860 set -- $args 861 eval "$RESTARTCMD" 862 else 863 echo "$RESTARTCMD" | 864 sed -e '/^$/d' -e 's/^ //g' 865 fi 866 exit $? 867fi 868 869# Not normally needed, but subscribers should be able to run independently 870if [ "$cmd" = v ] || [ -n "$VFLAG" ]; then 871 make_vars "$iface" 872 exit $? 873fi 874 875# Test that we have valid options 876case "$cmd" in 877a|d|C|c) 878 if [ -z "$iface" ]; then 879 error_exit "Interface not specified" 880 fi 881 ;; 882u) ;; 883*) 884 if [ -n "$cmd" ] && [ "$cmd" != h ]; then 885 error_exit "Unknown option $cmd" 886 fi 887 usage 888 ;; 889esac 890 891if [ "$cmd" = a ]; then 892 for x in '/' \\ ' ' '*'; do 893 case "$iface" in 894 *[$x]*) error_exit "$x not allowed in interface name";; 895 esac 896 done 897 for x in '.' '-' '~'; do 898 case "$iface" in 899 [$x]*) error_exit \ 900 "$x not allowed at start of interface name";; 901 esac 902 done 903 [ "$cmd" = a ] && [ -t 0 ] && error_exit "No file given via stdin" 904fi 905 906if [ ! -d "$VARDIR" ]; then 907 if [ -L "$VARDIR" ]; then 908 dir="$(readlink "$VARDIR")" 909 # link maybe relative 910 cd "${VARDIR%/*}" 911 if ! mkdir -m 0755 -p "$dir"; then 912 error_exit "Failed to create needed" \ 913 "directory $dir" 914 fi 915 else 916 if ! mkdir -m 0755 -p "$VARDIR"; then 917 error_exit "Failed to create needed" \ 918 "directory $VARDIR" 919 fi 920 fi 921fi 922 923if [ ! -d "$IFACEDIR" ]; then 924 mkdir -m 0755 -p "$IFACEDIR" || \ 925 error_exit "Failed to create needed directory $IFACEDIR" 926 if [ "$cmd" = d ]; then 927 # Provide the same error messages as below 928 if ! ${force}; then 929 cd "$IFACEDIR" 930 for i in $args; do 931 warn "No resolv.conf for interface $i" 932 done 933 fi 934 ${force} 935 exit $? 936 fi 937fi 938 939# An interface was added, changed, deleted or a general update was called. 940# Due to exclusivity we need to ensure that this is an atomic operation. 941# Our subscribers *may* need this as well if the init system is sub par. 942# As such we spinlock at this point as best we can. 943# We don't use flock(1) because it's not widely available and normally resides 944# in /usr which we do our very best to operate without. 945[ -w "$VARDIR" ] || error_exit "Cannot write to $LOCKDIR" 946: ${lock_timeout:=10} 947: ${clear_nopids:=5} 948have_pid=false 949had_pid=false 950while true; do 951 if mkdir "$LOCKDIR" 2>/dev/null; then 952 trap 'rm -rf "$LOCKDIR";' EXIT 953 trap 'rm -rf "$LOCKDIR"; exit 1' INT QUIT ABRT SEGV ALRM TERM 954 echo $$ >"$LOCKDIR/pid" 955 break 956 fi 957 pid=$(cat "$LOCKDIR/pid" 2>/dev/null) 958 if [ "$pid" -gt 0 ] 2>/dev/null; then 959 have_pid=true 960 had_pid=true 961 else 962 have_pid=false 963 clear_nopids=$(($clear_nopids - 1)) 964 if [ "$clear_nopids" -le 0 ]; then 965 warn "not seen a pid, clearing lock directory" 966 rm -rf "$LOCKDIR" 967 else 968 lock_timeout=$(($lock_timeout - 1)) 969 sleep 1 970 fi 971 continue 972 fi 973 if $have_pid && ! kill -0 "$pid"; then 974 warn "clearing stale lock pid $pid" 975 rm -rf "$LOCKDIR" 976 continue 977 fi 978 lock_timeout=$(($lock_timeout - 1)) 979 if [ "$lock_timeout" -le 0 ]; then 980 if $have_pid; then 981 error_exit "timed out waiting for lock from pid $pid" 982 else 983 if $had_pid; then 984 error_exit "timed out waiting for lock" \ 985 "from some pids" 986 else 987 error_exit "timed out waiting for lock" 988 fi 989 fi 990 fi 991 sleep 1 992done 993unset have_pid had_pid clear_nopids 994 995case "$cmd" in 996a) 997 # Read resolv.conf from stdin 998 resolv="$(cat)" 999 changed=false 1000 changedfile=false 1001 # If what we are given matches what we have, then do nothing 1002 if [ -e "$IFACEDIR/$iface" ]; then 1003 if [ "$(echo "$resolv")" != \ 1004 "$(cat "$IFACEDIR/$iface")" ] 1005 then 1006 changed=true 1007 changedfile=true 1008 fi 1009 else 1010 changed=true 1011 changedfile=true 1012 fi 1013 1014 # Set metric and private before creating the interface resolv.conf file 1015 # to ensure that it will have the correct flags 1016 [ ! -d "$METRICDIR" ] && mkdir "$METRICDIR" 1017 oldmetric="$METRICDIR/"*" $iface" 1018 newmetric= 1019 if [ -n "$IF_METRIC" ]; then 1020 # Pad metric to 6 characters, so 5 is less than 10 1021 while [ ${#IF_METRIC} -le 6 ]; do 1022 IF_METRIC="0$IF_METRIC" 1023 done 1024 newmetric="$METRICDIR/$IF_METRIC $iface" 1025 fi 1026 rm -f "$METRICDIR/"*" $iface" 1027 [ "$oldmetric" != "$newmetric" ] && 1028 [ "$oldmetric" != "$METRICDIR/* $iface" ] && 1029 changed=true 1030 [ -n "$newmetric" ] && echo " " >"$newmetric" 1031 1032 case "$IF_PRIVATE" in 1033 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) 1034 if [ ! -d "$PRIVATEDIR" ]; then 1035 [ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR" 1036 mkdir "$PRIVATEDIR" 1037 fi 1038 [ -e "$PRIVATEDIR/$iface" ] || changed=true 1039 [ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface" 1040 ;; 1041 *) 1042 if [ -e "$PRIVATEDIR/$iface" ]; then 1043 rm -f "$PRIVATEDIR/$iface" 1044 changed=true 1045 fi 1046 ;; 1047 esac 1048 1049 oldexcl= 1050 for x in "$EXCLUSIVEDIR/"*" $iface"; do 1051 if [ -f "$x" ]; then 1052 oldexcl="$x" 1053 break 1054 fi 1055 done 1056 case "$IF_EXCLUSIVE" in 1057 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) 1058 if [ ! -d "$EXCLUSIVEDIR" ]; then 1059 [ -e "$EXCLUSIVEDIR" ] && rm "$EXCLUSIVEDIR" 1060 mkdir "$EXCLUSIVEDIR" 1061 fi 1062 cd "$EXCLUSIVEDIR" 1063 for x in *; do 1064 [ -f "$x" ] && break 1065 done 1066 if [ "${x#* }" != "$iface" ]; then 1067 if [ "$x" = "${x% *}" ]; then 1068 x=10000000 1069 else 1070 x="${x% *}" 1071 fi 1072 if [ "$x" = "0000000" ]; then 1073 warn "exclusive underflow" 1074 else 1075 x=$(($x - 1)) 1076 fi 1077 if [ -d "$EXCLUSIVEDIR" ]; then 1078 echo " " >"$EXCLUSIVEDIR/$x $iface" 1079 fi 1080 changed=true 1081 fi 1082 ;; 1083 *) 1084 if [ -f "$oldexcl" ]; then 1085 rm -f "$oldexcl" 1086 changed=true 1087 fi 1088 ;; 1089 esac 1090 1091 if $changedfile; then 1092 printf "%s\n" "$resolv" >"$IFACEDIR/$iface" || exit $? 1093 elif ! $changed; then 1094 exit 0 1095 fi 1096 unset changed changedfile oldmetric newmetric x oldexcl 1097 ;; 1098 1099d) 1100 # Delete any existing information about the interface 1101 cd "$IFACEDIR" 1102 changed=false 1103 for i in $args; do 1104 if [ -e "$i" ]; then 1105 changed=true 1106 elif ! ${force}; then 1107 warn "No resolv.conf for interface $i" 1108 fi 1109 rm -f "$i" "$METRICDIR/"*" $i" \ 1110 "$PRIVATEDIR/$i" \ 1111 "$EXCLUSIVEDIR/"*" $i" || exit $? 1112 done 1113 1114 if ! $changed; then 1115 # Set the return code based on the forced flag 1116 $force 1117 exit $? 1118 fi 1119 unset changed i 1120 ;; 1121 1122C) 1123 # Mark interface as deprecated 1124 [ ! -d "$DEPRECATEDDIR" ] && mkdir "$DEPRECATEDDIR" 1125 cd "$DEPRECATEDDIR" 1126 changed=false 1127 for i in $args; do 1128 if [ ! -e "$i" ]; then 1129 changed=true 1130 echo " " >"$i" || exit $? 1131 fi 1132 done 1133 $changed || exit 0 1134 unset changed i 1135 ;; 1136 1137c) 1138 # Mark interface as active 1139 if [ -d "$DEPRECATEDDIR" ]; then 1140 cd "$DEPRECATEDDIR" 1141 changed=false 1142 for i in $args; do 1143 if [ -e "$i" ]; then 1144 changed=true 1145 rm "$i" || exit $? 1146 fi 1147 done 1148 $changed || exit 0 1149 unset changed i 1150 fi 1151 ;; 1152esac 1153 1154case "${resolvconf:-YES}" in 1155[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;; 1156*) exit 0;; 1157esac 1158 1159# Try and detect a suitable init system for our scripts 1160detect_init 1161export RESTARTCMD RCDIR _NOINIT_WARNED 1162 1163eval "$(make_vars)" 1164export RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS 1165: ${list_resolv:=list_resolv -L} 1166retval=0 1167 1168# Run scripts in the same directory resolvconf is run from 1169# in case any scripts accidentally dump files in the wrong place. 1170cd "$_PWD" 1171for script in "$LIBEXECDIR"/*; do 1172 if [ -f "$script" ]; then 1173 eval script_enabled="\$${script##*/}" 1174 case "${script_enabled:-YES}" in 1175 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;; 1176 *) continue;; 1177 esac 1178 if [ -x "$script" ]; then 1179 "$script" "$cmd" "$iface" 1180 else 1181 (set -- "$cmd" "$iface"; . "$script") 1182 fi 1183 retval=$(($retval + $?)) 1184 fi 1185done 1186exit $retval 1187