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