aboutsummaryrefslogtreecommitdiff
path: root/resolvconf.in
diff options
context:
space:
mode:
Diffstat (limited to 'resolvconf.in')
-rw-r--r--resolvconf.in475
1 files changed, 392 insertions, 83 deletions
diff --git a/resolvconf.in b/resolvconf.in
index e24a90b9cb65..3b2b0f53fd83 100644
--- a/resolvconf.in
+++ b/resolvconf.in
@@ -1,5 +1,5 @@
#!/bin/sh
-# Copyright (c) 2007-2011 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#* }"
;;
*)
@@ -226,40 +249,67 @@ 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="$*"
$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
@@ -269,23 +319,134 @@ list_resolv()
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
@@ -293,28 +454,50 @@ 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:Dd: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
@@ -343,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
@@ -357,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
@@ -372,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
@@ -451,6 +755,11 @@ 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