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