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