diff options
Diffstat (limited to 'contrib/openresolv/resolvconf.in')
-rw-r--r-- | contrib/openresolv/resolvconf.in | 524 |
1 files changed, 437 insertions, 87 deletions
diff --git a/contrib/openresolv/resolvconf.in b/contrib/openresolv/resolvconf.in index 8a608d3..3b2b0f5 100644 --- a/contrib/openresolv/resolvconf.in +++ b/contrib/openresolv/resolvconf.in @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright (c) 2007-2009 Roy Marples +# Copyright (c) 2007-2015 Roy Marples # All rights reserved # Redistribution and use in source and binary forms, with or without @@ -28,6 +28,17 @@ RESOLVCONF="$0" SYSCONFDIR=@SYSCONFDIR@ LIBEXECDIR=@LIBEXECDIR@ VARDIR=@VARDIR@ + +# Disregard dhcpcd setting +unset interface_order state_dir + +# If you change this, change the test in VFLAG and libc.in as well +local_nameservers="127.* 0.0.0.0 255.255.255.255 ::1" + +dynamic_order="tap[0-9]* tun[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]*" +interface_order="lo lo[0-9]*" +name_server_blacklist="0.0.0.0" + # Support original resolvconf configuration layout # as well as the openresolv config file if [ -f "$SYSCONFDIR"/resolvconf.conf ]; then @@ -39,12 +50,17 @@ elif [ -d "$SYSCONFDIR/resolvconf" ]; then interface_order="$(cat "$SYSCONFDIR"/interface-order)" fi fi +TMPDIR="$VARDIR/tmp" IFACEDIR="$VARDIR/interfaces" METRICDIR="$VARDIR/metrics" PRIVATEDIR="$VARDIR/private" +EXCLUSIVEDIR="$VARDIR/exclusive" +LOCKDIR="$VARDIR/lock" -: ${dynamic_order:=tap[0-9]* tun[0-9]* vpn vpn[0-9]* ppp[0-9]* ippp[0-9]*} -: ${interface_order:=lo lo[0-9]*} +warn() +{ + echo "$*" >&2 +} error_exit() { @@ -64,6 +80,7 @@ usage() (DNS supplied via stdin in resolv.conf format) -m metric Give the added DNS information a metric -p Mark the interface as private + -x Mark the interface as exclusive -d \$INTERFACE Delete DNS information from the specified interface -f Ignore non existant interfaces -I Init the state dir @@ -84,16 +101,23 @@ usage() echo_resolv() { - local line= - [ -n "$1" -a -e "$IFACEDIR/$1" ] || return 1 + local line= OIFS="$IFS" + + [ -n "$1" -a -f "$IFACEDIR/$1" ] || return 1 echo "# resolv.conf from $1" # Our variable maker works of the fact each resolv.conf per interface # is separated by blank lines. # So we remove them when echoing them. - while read line; do - [ -n "$line" ] && echo "$line" + while read -r line; do + IFS="$OIFS" + if [ -n "$line" ]; then + # We need to set IFS here to preserve any whitespace + IFS='' + printf "%s\n" "$line" + fi done < "$IFACEDIR/$1" echo + IFS="$OIFS" } # Parse resolv.conf's and make variables @@ -101,22 +125,11 @@ echo_resolv() parse_resolv() { local line= ns= ds= search= d= n= newns= - local new=true iface= private=false p= + local new=true iface= private=false p= domain= l= islocal= - echo "DOMAINS=" - echo "SEARCH=\"$search_domains\"" - # let our subscribers know about global nameservers - for n in $name_servers; do - case "$n" in - 127.*|0.0.0.0|255.255.255.255|::1) :;; - *) newns="$newns${newns:+ }$n";; - esac - done - echo "NAMESERVERS=\"$newns\"" - echo "LOCALNAMESERVERS=" newns= - while read line; do + while read -r line; do case "$line" in "# resolv.conf from "*) if ${new}; then @@ -129,24 +142,34 @@ parse_resolv() cd "$IFACEDIR" private=false for p in $private_interfaces; do - if [ "$p" = "$iface" ]; then - private=true - break - fi + case "$iface" in + "$p"|"$p":*) private=true; break;; + esac done fi fi ;; "nameserver "*) - case "${line#* }" in - 127.*|0.0.0.0|255.255.255.255|::1) - echo "LOCALNAMESERVERS=\"\$LOCALNAMESERVERS ${line#* }\"" - continue - ;; - esac - ns="$ns${line#* } " + islocal=false + for l in $local_nameservers; do + case "${line#* }" in + $l) + islocal=true + echo "LOCALNAMESERVERS=\"\$LOCALNAMESERVERS ${line#* }\"" + break + ;; + esac + done + $islocal || ns="$ns${line#* } " ;; - "domain "*|"search "*) + "domain "*) + if [ -z "$domain" ]; then + domain="${line#* }" + echo "DOMAIN=\"$domain\"" + fi + search="${line#* }" + ;; + "search "*) search="${line#* }" ;; *) @@ -187,70 +210,243 @@ uniqify() echo "${result# *}" } +dirname() +{ + local dir= OIFS="$IFS" + local IFS=/ + set -- $@ + IFS="$OIFS" + if [ -n "$1" ]; then + printf %s . + else + shift + fi + while [ -n "$2" ]; do + printf "/%s" "$1" + shift + done + printf "\n" +} + +config_mkdirs() +{ + local e=0 f d + for f; do + [ -n "$f" ] || continue + d="$(dirname "$f")" + if [ ! -d "$d" ]; then + if type install >/dev/null 2>&1; then + install -d "$d" || e=$? + else + mkdir "$d" || e=$? + fi + fi + done + return $e +} + list_resolv() { [ -d "$IFACEDIR" ] || return 0 - local report=false list= retval=0 cmd="$1" + local report=false list= retval=0 cmd="$1" excl= shift + case "$IF_EXCLUSIVE" in + [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) + if [ -d "$EXCLUSIVEDIR" ]; then + cd "$EXCLUSIVEDIR" + for i in *; do + if [ -f "$i" ]; then + list="${i#* }" + break + fi + done + fi + excl=true + ;; + *) + excl=false + ;; + esac + # If we have an interface ordering list, then use that. # It works by just using pathname expansion in the interface directory. if [ -n "$1" ]; then - list="$@" + list="$*" $force || report=true - else + elif ! $excl; then cd "$IFACEDIR" for i in $interface_order; do - [ -e "$i" ] && list="$list $i" + [ -f "$i" ] && list="$list $i" + for ii in "$i":* "$i".*; do + [ -f "$ii" ] && list="$list $ii" + done done for i in $dynamic_order; do if [ -e "$i" -a ! -e "$METRICDIR/"*" $i" ]; then list="$list $i" fi + for ii in "$i":* "$i".*; do + if [ -f "$ii" -a ! -e "$METRICDIR/"*" $ii" ]; then + list="$list $ii" + fi + done done if [ -d "$METRICDIR" ]; then cd "$METRICDIR" for i in *; do - list="$list ${i#* }" + [ -f "$i" ] && list="$list ${i#* }" done fi list="$list *" fi cd "$IFACEDIR" + retval=1 for i in $(uniqify $list); do # Only list interfaces which we really have - if ! [ -e "$i" ]; then + if ! [ -f "$i" ]; then if $report; then echo "No resolv.conf for interface $i" >&2 - retval=$(($retval + 1)) + retval=2 fi continue fi if [ "$cmd" = i -o "$cmd" = "-i" ]; then - printf "$i " + printf %s "$i " else echo_resolv "$i" fi + [ $? = 0 -a "$retval" = 1 ] && retval=0 done [ "$cmd" = i -o "$cmd" = "-i" ] && echo return $retval } +list_remove() { + local list= e= l= result= found= retval=0 + + [ -z "$2" ] && return 0 + eval list=\"\$$1\" + shift + + set -f + for e; do + found=false + for l in $list; do + case "$e" in + $l) found=true;; + esac + $found && break + done + if $found; then + retval=$(($retval + 1)) + else + result="$result $e" + fi + done + set +f + echo "${result# *}" + return $retval +} + +echo_prepend() +{ + echo "# Generated by resolvconf" + if [ -n "$search_domains" ]; then + echo "search $search_domains" + fi + for n in $name_servers; do + echo "nameserver $n" + done + echo +} + +echo_append() +{ + echo "# Generated by resolvconf" + if [ -n "$search_domains_append" ]; then + echo "search $search_domains_append" + fi + for n in $name_servers_append; do + echo "nameserver $n" + done + echo +} + +replace() +{ + local r= k= f= v= val= sub= + + while read -r keyword value; do + for r in $replace; do + k="${r%%/*}" + r="${r#*/}" + f="${r%%/*}" + r="${r#*/}" + v="${r%%/*}" + case "$keyword" in + $k) + case "$value" in + $f) value="$v";; + esac + ;; + esac + done + val= + for sub in $value; do + for r in $replace_sub; do + k="${r%%/*}" + r="${r#*/}" + f="${r%%/*}" + r="${r#*/}" + v="${r%%/*}" + case "$keyword" in + $k) + case "$sub" in + $f) sub="$v";; + esac + ;; + esac + done + val="$val${val:+ }$sub" + done + printf "%s %s\n" "$keyword" "$val" + done +} + make_vars() { - eval "$(list_resolv -l "$@" | parse_resolv)" + local newdomains= d= dn= newns= ns= + + # Clear variables + DOMAIN= + DOMAINS= + SEARCH= + NAMESERVERS= + LOCALNAMESERVERS= + + if [ -n "$name_servers" -o -n "$search_domains" ]; then + eval "$(echo_prepend | parse_resolv)" + fi + if [ -z "$VFLAG" ]; then + IF_EXCLUSIVE=1 + list_resolv -i "$@" >/dev/null || IF_EXCLUSIVE=0 + eval "$(list_resolv -l "$@" | replace | parse_resolv)" + fi + if [ -n "$name_servers_append" -o -n "$search_domains_append" ]; then + eval "$(echo_append | parse_resolv)" + fi # Ensure that we only list each domain once - newdomains= for d in $DOMAINS; do dn="${d%%:*}" + list_remove domain_blacklist "$dn" >/dev/null || continue case " $newdomains" in *" ${dn}:"*) continue;; esac - newdomains="$newdomains${newdomains:+ }$dn:" newns= for nd in $DOMAINS; do if [ "$dn" = "${nd%%:*}" ]; then @@ -258,34 +454,56 @@ make_vars() while [ -n "$ns" ]; do case ",$newns," in *,${ns%%,*},*) ;; - *) newns="$newns${newns:+,}${ns%%,*}";; + *) list_remove name_server_blacklist \ + "${ns%%,*}" >/dev/null \ + && newns="$newns${newns:+,}${ns%%,*}";; esac [ "$ns" = "${ns#*,}" ] && break ns="${ns#*,}" done fi done - newdomains="$newdomains$newns" + if [ -n "$newns" ]; then + newdomains="$newdomains${newdomains:+ }$dn:$newns" + fi done + DOMAIN="$(list_remove domain_blacklist $DOMAIN)" + SEARCH="$(uniqify $SEARCH)" + SEARCH="$(list_remove domain_blacklist $SEARCH)" + NAMESERVERS="$(uniqify $NAMESERVERS)" + NAMESERVERS="$(list_remove name_server_blacklist $NAMESERVERS)" + LOCALNAMESERVERS="$(uniqify $LOCALNAMESERVERS)" + LOCALNAMESERVERS="$(list_remove name_server_blacklist $LOCALNAMESERVERS)" + echo "DOMAIN='$DOMAIN'" + echo "SEARCH='$SEARCH'" + echo "NAMESERVERS='$NAMESERVERS'" + echo "LOCALNAMESERVERS='$LOCALNAMESERVERS'" echo "DOMAINS='$newdomains'" - echo "SEARCH='$(uniqify $SEARCH)'" - echo "NAMESERVERS='$(uniqify $NAMESERVERS)'" - echo "LOCALNAMESERVERS='$(uniqify $LOCALNAMESERVERS)'" } force=false -while getopts a:d:fhIilm:puv OPT; do +VFLAG= +while getopts a:Dd:fhIilm:puvVx OPT; do case "$OPT" in f) force=true;; h) usage;; m) IF_METRIC="$OPTARG";; p) IF_PRIVATE=1;; + V) + VFLAG=1 + if [ "$local_nameservers" = \ + "127.* 0.0.0.0 255.255.255.255 ::1" ] + then + local_nameservers= + fi + ;; + x) IF_EXCLUSIVE=1;; '?') ;; *) cmd="$OPT"; iface="$OPTARG";; esac done shift $(($OPTIND - 1)) -args="$iface${iface:+ }$@" +args="$iface${iface:+ }$*" # -I inits the state dir if [ "$cmd" = I ]; then @@ -295,6 +513,12 @@ if [ "$cmd" = I ]; then exit $? fi +# -D ensures that the listed config file base dirs exist +if [ "$cmd" = D ]; then + config_mkdirs "$@" + exit $? +fi + # -l lists our resolv files, optionally for a specific interface if [ "$cmd" = l -o "$cmd" = i ]; then list_resolv "$cmd" "$args" @@ -302,7 +526,7 @@ if [ "$cmd" = l -o "$cmd" = i ]; then fi # Not normally needed, but subscribers should be able to run independently -if [ "$cmd" = v ]; then +if [ "$cmd" = v -o -n "$VFLAG" ]; then make_vars "$iface" exit $? fi @@ -316,6 +540,7 @@ elif [ "$cmd" != u ]; then [ -n "$cmd" -a "$cmd" != h ] && usage "Unknown option $cmd" usage fi + if [ "$cmd" = a ]; then for x in '/' \\ ' ' '*'; do case "$iface" in @@ -331,78 +556,198 @@ if [ "$cmd" = a ]; then [ "$cmd" = a -a -t 0 ] && error_exit "No file given via stdin" fi -if [ ! -d "$IFACEDIR" ]; then - if [ ! -d "$VARDIR" ]; then - if [ -L "$VARDIR" ]; then - dir="$(readlink "$VARDIR")" - # link maybe relative - cd "${VARDIR%/*}" - if ! mkdir -m 0755 -p "$dir"; then - error_exit "Failed to create needed" \ - "directory $dir" - fi - else - if ! mkdir -m 0755 -p "$VARDIR"; then - error_exit "Failed to create needed" \ - "directory $VARDIR" - fi +if [ ! -d "$VARDIR" ]; then + if [ -L "$VARDIR" ]; then + dir="$(readlink "$VARDIR")" + # link maybe relative + cd "${VARDIR%/*}" + if ! mkdir -m 0755 -p "$dir"; then + error_exit "Failed to create needed" \ + "directory $dir" + fi + else + if ! mkdir -m 0755 -p "$VARDIR"; then + error_exit "Failed to create needed" \ + "directory $VARDIR" fi fi +fi + +if [ ! -d "$IFACEDIR" ]; then mkdir -m 0755 -p "$IFACEDIR" || \ error_exit "Failed to create needed directory $IFACEDIR" -else - # Delete any existing information about the interface if [ "$cmd" = d ]; then - cd "$IFACEDIR" - for i in $args; do - if [ "$cmd" = d -a ! -e "$i" ]; then - $force && continue - error_exit "No resolv.conf for" \ - "interface $i" - fi - rm -f "$i" "$METRICDIR/"*" $i" \ - "$PRIVATEDIR/$i" || exit $? - done + # Provide the same error messages as below + if ! ${force}; then + cd "$IFACEDIR" + for i in $args; do + warn "No resolv.conf for interface $i" + done + fi + ${force} + exit $? fi fi -if [ "$cmd" = a ]; then +# An interface was added, changed, deleted or a general update was called. +# Due to exclusivity we need to ensure that this is an atomic operation. +# Our subscribers *may* need this as well if the init system is sub par. +# As such we spinlock at this point as best we can. +# We don't use flock(1) because it's not widely available and normally resides +# in /usr which we do our very best to operate without. +[ -w "$VARDIR" ] || error_exit "Cannot write to $LOCKDIR" +: ${lock_timeout:=10} +while true; do + if mkdir "$LOCKDIR" 2>/dev/null; then + trap 'rm -rf "$LOCKDIR";' EXIT + trap 'rm -rf "$LOCKDIR"; exit 1' INT QUIT ABRT SEGV ALRM TERM + echo $$ >"$LOCKDIR/pid" + break + fi + pid=$(cat "$LOCKDIR/pid") + if ! kill -0 "$pid"; then + warn "clearing stale lock pid $pid" + rm -rf "$LOCKDIR" + continue + fi + lock_timeout=$(($lock_timeout - 1)) + if [ "$lock_timeout" -le 0 ]; then + error_exit "timed out waiting for lock from pid $pid" + fi + sleep 1 +done + +case "$cmd" in +a) # Read resolv.conf from stdin resolv="$(cat)" + changed=false + changedfile=false # If what we are given matches what we have, then do nothing if [ -e "$IFACEDIR/$iface" ]; then - if [ "$(echo "$resolv")" = \ + if [ "$(echo "$resolv")" != \ "$(cat "$IFACEDIR/$iface")" ] then - exit 0 + changed=true + changedfile=true fi - rm "$IFACEDIR/$iface" + else + changed=true + changedfile=true fi - echo "$resolv" >"$IFACEDIR/$iface" || exit $? + + # Set metric and private before creating the interface resolv.conf file + # to ensure that it will have the correct flags [ ! -d "$METRICDIR" ] && mkdir "$METRICDIR" - rm -f "$METRICDIR/"*" $iface" + oldmetric="$METRICDIR/"*" $iface" + newmetric= if [ -n "$IF_METRIC" ]; then # Pad metric to 6 characters, so 5 is less than 10 while [ ${#IF_METRIC} -le 6 ]; do IF_METRIC="0$IF_METRIC" done - echo " " >"$METRICDIR/$IF_METRIC $iface" + newmetric="$METRICDIR/$IF_METRIC $iface" fi + rm -f "$METRICDIR/"*" $iface" + [ "$oldmetric" != "$newmetric" -a \ + "$oldmetric" != "$METRICDIR/* $iface" ] && + changed=true + [ -n "$newmetric" ] && echo " " >"$newmetric" + case "$IF_PRIVATE" in [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) if [ ! -d "$PRIVATEDIR" ]; then [ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR" mkdir "$PRIVATEDIR" fi + [ -e "$PRIVATEDIR/$iface" ] || changed=true [ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$iface" ;; *) if [ -e "$PRIVATEDIR/$iface" ]; then rm -f "$PRIVATEDIR/$iface" + changed=true fi ;; esac -fi + + oldexcl= + for x in "$EXCLUSIVEDIR/"*" $iface"; do + if [ -f "$x" ]; then + oldexcl="$x" + break + fi + done + case "$IF_EXCLUSIVE" in + [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) + if [ ! -d "$EXCLUSIVEDIR" ]; then + [ -e "$EXCLUSIVEDIR" ] && rm "$EXCLUSIVEDIR" + mkdir "$EXCLUSIVEDIR" + fi + cd "$EXCLUSIVEDIR" + for x in *; do + [ -f "$x" ] && break + done + if [ "${x#* }" != "$iface" ]; then + if [ "$x" = "${x% *}" ]; then + x=10000000 + else + x="${x% *}" + fi + if [ "$x" = "0000000" ]; then + warn "exclusive underflow" + else + x=$(($x - 1)) + fi + if [ -d "$EXCLUSIVEDIR" ]; then + echo " " >"$EXCLUSIVEDIR/$x $iface" + fi + changed=true + fi + ;; + *) + if [ -f "$oldexcl" ]; then + rm -f "$oldexcl" + changed=true + fi + ;; + esac + + if $changedfile; then + printf "%s\n" "$resolv" >"$IFACEDIR/$iface" || exit $? + elif ! $changed; then + exit 0 + fi + unset changed changedfile oldmetric newmetric x oldexcl + ;; + +d) + # Delete any existing information about the interface + cd "$IFACEDIR" + changed=false + for i in $args; do + if [ -e "$i" ]; then + changed=true + elif ! ${force}; then + warn "No resolv.conf for interface $i" + fi + rm -f "$i" "$METRICDIR/"*" $i" \ + "$PRIVATEDIR/$i" \ + "$EXCLUSIVEDIR/"*" $i" || exit $? + done + if ! ${changed}; then + # Set the return code based on the forced flag + ${force} + exit $? + fi + unset changed i + ;; +esac + +case "${resolvconf:-YES}" in +[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;; +*) exit 0;; +esac eval "$(make_vars)" export RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS @@ -410,10 +755,15 @@ export RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS retval=0 for script in "$LIBEXECDIR"/*; do if [ -f "$script" ]; then + eval script_enabled="\$${script##*/}" + case "${script_enabled:-YES}" in + [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;; + *) continue;; + esac if [ -x "$script" ]; then "$script" "$cmd" "$iface" else - (. "$script" "$cmd" "$iface") + (set -- "$cmd" "$iface"; . "$script") fi retval=$(($retval + $?)) fi |