resolvconf.in revision 1.15 1 #!/bin/sh
2 # Copyright (c) 2007-2025 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
27 RESOLVCONF="$0"
28 OPENRESOLV_VERSION="3.17.1"
29 SYSCONFDIR=@SYSCONFDIR@
30 LIBEXECDIR=@LIBEXECDIR@
31 VARDIR=@VARDIR@
32 RCDIR=@RCDIR@
33 RESTARTCMD=@RESTARTCMD@
34
35 if [ "$1" = "--version" ]; then
36 echo "openresolv $OPENRESOLV_VERSION"
37 echo "Copyright (c) 2007-2025 Roy Marples"
38 exit 0
39 fi
40
41 # Disregard dhcpcd setting
42 unset interface_order state_dir
43
44 # If you change this, change the test in VFLAG and libc.in as well
45 local_nameservers="127.* 0.0.0.0 255.255.255.255 ::1"
46
47 dynamic_order="tap[0-9]* tun[0-9]* vpn vpn[0-9]* wg[0-9]* ppp[0-9]* ippp[0-9]*"
48 interface_order="lo lo[0-9]*"
49 name_server_blacklist="0.0.0.0"
50
51 # Poor mans cat
52 # /usr might not be available
53 cat()
54 {
55 OIFS="$IFS"
56 IFS=''
57 if [ -n "$1" ]; then
58 while read -r line; do
59 printf "%s\n" "$line"
60 done < "$1"
61 else
62 while read -r line; do
63 printf "%s\n" "$line"
64 done
65 fi
66 retval=$?
67 IFS="$OIFS"
68 return $retval
69 }
70
71
72 # Support original resolvconf configuration layout
73 # as well as the openresolv config file
74 if [ -f "$SYSCONFDIR"/resolvconf.conf ]; then
75 . "$SYSCONFDIR"/resolvconf.conf
76 [ -n "$state_dir" ] && VARDIR="$state_dir"
77 elif [ -d "$SYSCONFDIR/resolvconf" ]; then
78 SYSCONFDIR="$SYSCONFDIR/resolvconf"
79 if [ -f "$SYSCONFDIR"/interface-order ]; then
80 interface_order="$(cat "$SYSCONFDIR"/interface-order)"
81 fi
82 fi
83
84 KEYDIR="$VARDIR/keys"
85 METRICDIR="$VARDIR/metrics"
86 PRIVATEDIR="$VARDIR/private"
87 NOSEARCHDIR="$VARDIR/nosearch"
88 EXCLUSIVEDIR="$VARDIR/exclusive"
89 DEPRECATEDDIR="$VARDIR/deprecated"
90 LOCKDIR="$VARDIR/lock"
91 _PWD="$PWD"
92
93 # Compat
94 if [ ! -d "$KEYDIR" ] && [ -d "$VARDIR/interfaces" ]; then
95 KEYDIR="$VARDIR/interfaces"
96 fi
97 : ${allow_keys:="$allow_interfaces"}
98 : ${deny_keys:="$deny_interfaces"}
99 : ${key_order:="$interface_order"}
100 : ${inclusive_keys:="$inclusive_interfaces"}
101 : ${exclusive_keys:="$exclusive_interfaces"}
102 : ${private_keys:="$private_interfaces"}
103 : ${public_keys:="$public_interfaces"}
104
105 warn()
106 {
107 echo "$@" >&2
108 }
109
110 error_exit()
111 {
112 echo "$@" >&2
113 exit 1
114 }
115
116 usage()
117 {
118 cat <<-EOF
119 Usage: ${RESOLVCONF##*/} [options] command [argument]
120
121 Inform the system about any DNS updates.
122
123 Commands:
124 -a \$KEY Add DNS information to the specified key
125 (DNS supplied via stdin in resolv.conf format)
126 -C \$PATTERN Deprecate DNS information for matched key
127 -c \$PATTERN Configure DNS information for matched key
128 -d \$PATTERN Delete DNS information from the matched key
129 -h Show this help cruft
130 -i [\$PATTERN] Show keys that have supplied DNS information
131 optionally from keys that match the specified
132 pattern
133 -l [\$PATTERN] Show DNS information, optionally from keys
134 that match the specified pattern
135 -L [\$PATTERN] Same as -l, but adjusted by our config
136
137 -u Run updates from our current DNS information
138 --version Echo the ${RESOLVCONF##*/} version
139
140 Options:
141 -f Ignore non existent keys
142 -m metric Give the added DNS information a metric
143 -p Mark the resolv.conf as private
144 -x Mark the resolv.conf as exclusive
145
146 Subscriber and System Init Commands:
147 -I Init the state dir
148 -r \$SERVICE Restart the system service
149 (restarting a non-existent or non-running service
150 should have no output and return 0)
151 -R Show the system service restart command
152 -v [\$PATTERN] echo NEWDOMAIN, NEWSEARCH and NEWNS variables to
153 the console
154 -V [\$PATTERN] Same as -v, but only uses configuration in
155 $SYSCONFDIR/resolvconf.conf
156 EOF
157 [ -z "$1" ] && exit 0
158 echo
159 error_exit "$@"
160 }
161
162 public_key() {
163 key="$1"
164
165 # Allow expansion
166 cd "$KEYDIR"
167
168 # Public keys override private ones.
169 for p in $public_keys; do
170 case "$key" in
171 "$p"|"$p":*) return 0;;
172 esac
173 done
174
175 return 1
176 }
177
178 private_key()
179 {
180 key="$1"
181
182 if public_key "$key"; then
183 return 1
184 fi
185
186 if [ -e "$PRIVATEDIR/$key" ]; then
187 return 0
188 fi
189
190 for p in $private_keys; do
191 case "$key" in
192 "$p"|"$p":*) return 0;;
193 esac
194 done
195
196 # Not a private key
197 return 1
198 }
199
200 nosearch_key()
201 {
202 key="$1"
203
204 if public_key "$key"; then
205 return 1
206 fi
207
208 if [ -e "$NOSEARCHDIR/$key" ]; then
209 return 0
210 fi
211
212 for p in $nosearch_keys; do
213 case "$key" in
214 "$p"|"$p":*) return 0;;
215 esac
216 done
217
218 # Not a non searchable key
219 return 1
220 }
221
222 exclusive_key()
223 {
224 key="$1"
225
226 for x in "$EXCLUSIVEDIR/"*" $key"; do
227 if [ -f "$x" ]; then
228 return 0
229 fi
230 done
231
232 # Not an exclusive key
233 return 1
234 }
235
236 # Parse resolv.conf's and make variables
237 # for domain name servers, search name servers and global nameservers
238 parse_resolv()
239 {
240 domain=
241 new=true
242 newns=
243 ns=
244 private=false
245 nosearch=false
246 search=
247
248 while read -r line; do
249 value="${line#* }"
250 case "$line" in
251 "# resolv.conf from "*)
252 if ${new}; then
253 key="${line#\# resolv.conf from *}"
254 new=false
255 if nosearch_key "$key"; then
256 private=true
257 nosearch=true
258 elif private_key "$key"; then
259 private=true
260 nosearch=false
261 else
262 private=false
263 nosearch=false
264 fi
265 fi
266 ;;
267 "nameserver "*)
268 islocal=false
269 for l in $local_nameservers; do
270 case "$value" in
271 $l)
272 islocal=true
273 break
274 ;;
275 esac
276 done
277 if $islocal; then
278 echo "LOCALNAMESERVERS=\"\$LOCALNAMESERVERS \"'$value'"
279 else
280 ns="$ns${ns:+ }$value"
281 fi
282 ;;
283 "domain "*)
284 search="$value"
285 if [ -z "$domain" ]; then
286 domain="$search"
287 if ! $nosearch; then
288 echo "DOMAIN='$domain'"
289 fi
290 fi
291 ;;
292 "search "*)
293 search="$value"
294 ;;
295 *)
296 [ -n "$line" ] && continue
297 if [ -n "$ns" ] && [ -n "$search" ]; then
298 newns=
299 for n in $ns; do
300 newns="$newns${newns:+,}$n"
301 done
302 ds=
303 for d in $search; do
304 ds="$ds${ds:+ }$d:$newns"
305 done
306 echo "DOMAINS=\"\$DOMAINS \"'$ds'"
307 fi
308 if ! $nosearch; then
309 echo "SEARCH=\"\$SEARCH \"'$search'"
310 fi
311 if ! $private; then
312 echo "NAMESERVERS=\"\$NAMESERVERS \"'$ns'"
313 fi
314 ns=
315 search=
316 new=true
317 ;;
318 esac
319 done
320 }
321
322 uniqify()
323 {
324 result=
325 while [ -n "$1" ]; do
326 case " $result " in
327 *" $1 "*);;
328 *) result="$result $1";;
329 esac
330 shift
331 done
332 echo "${result# *}"
333 }
334
335 dirname()
336 {
337 OIFS="$IFS"
338 IFS=/
339 set -- $@
340 IFS="$OIFS"
341 if [ -n "$1" ]; then
342 printf %s .
343 else
344 shift
345 fi
346 while [ -n "$2" ]; do
347 printf "/%s" "$1"
348 shift
349 done
350 printf "\n"
351 }
352
353 config_mkdirs()
354 {
355 for f; do
356 [ -n "$f" ] || continue
357 d="$(dirname "$f")"
358 if [ ! -d "$d" ]; then
359 mkdir -p "$d" || return $?
360 fi
361 done
362 return 0
363 }
364
365 # With the advent of alternative init systems, it's possible to have
366 # more than one installed. So we need to try and guess what one we're
367 # using unless overridden by configure.
368 # Note that restarting a service is a last resort - the subscribers
369 # should make a reasonable attempt to reconfigure the service via some
370 # method, normally SIGHUP.
371 detect_init()
372 {
373 [ -n "$RESTARTCMD" ] && return 0
374
375 # Detect the running init system.
376 # As systemd and OpenRC can be installed on top of legacy init
377 # systems we try to detect them first.
378 status="@STATUSARG@"
379 : ${status:=status}
380 if [ -x /bin/systemctl ] && [ -S /run/systemd/private ]; then
381 RESTARTCMD='
382 if /bin/systemctl --quiet is-active $1.service
383 then
384 /bin/systemctl restart $1.service
385 fi'
386 elif [ -x /usr/bin/systemctl ] && [ -S /run/systemd/private ]; then
387 RESTARTCMD='
388 if /usr/bin/systemctl --quiet is-active $1.service
389 then
390 /usr/bin/systemctl restart $1.service
391 fi'
392 elif [ -x /sbin/rc-service ] &&
393 { [ -s /libexec/rc/init.d/softlevel ] ||
394 [ -s /run/openrc/softlevel ]; }
395 then
396 RESTARTCMD='/sbin/rc-service -i $1 -- -Ds restart'
397 elif [ -x /usr/sbin/invoke-rc.d ]; then
398 RCDIR=/etc/init.d
399 RESTARTCMD='
400 if /usr/sbin/invoke-rc.d --quiet $1 status >/dev/null 2>&1
401 then
402 /usr/sbin/invoke-rc.d $1 restart
403 fi'
404 elif [ -x /usr/bin/s6-rc ] && [ -x /usr/bin/s6-svc ]; then
405 RESTARTCMD='
406 if s6-rc -a list 2>/dev/null | grep -qFx $1-srv
407 then
408 s6-svc -r /run/service/$1-srv
409 fi'
410 elif [ -x /sbin/service ]; then
411 # Old RedHat
412 RCDIR=/etc/init.d
413 RESTARTCMD='
414 if /sbin/service $1; then
415 /sbin/service $1 restart
416 fi'
417 elif [ -x /usr/sbin/service ]; then
418 # Could be FreeBSD
419 RESTARTCMD="
420 if /usr/sbin/service \$1 $status >/dev/null 2>&1
421 then
422 /usr/sbin/service \$1 restart
423 fi"
424 elif [ -x /bin/sv ]; then
425 RESTARTCMD='/bin/sv status $1 >/dev/null 2>&1 &&
426 /bin/sv try-restart $1'
427 elif [ -x /usr/bin/sv ]; then
428 RESTARTCMD='/usr/bin/sv status $1 >/dev/null 2>&1 &&
429 /usr/bin/sv try-restart $1'
430 elif [ -e /etc/arch-release ] && [ -d /etc/rc.d ]; then
431 RCDIR=/etc/rc.d
432 RESTARTCMD='
433 if [ -e /var/run/daemons/$1 ]
434 then
435 /etc/rc.d/$1 restart
436 fi'
437 elif [ -e /etc/slackware-version ] && [ -d /etc/rc.d ]; then
438 RESTARTCMD='
439 if /etc/rc.d/rc.$1 status >/dev/null 2>&1
440 then
441 /etc/rc.d/rc.$1 restart
442 fi'
443 elif [ -e /etc/rc.d/rc.subr ] && [ -d /etc/rc.d ]; then
444 # OpenBSD
445 RESTARTCMD='
446 if /etc/rc.d/$1 check >/dev/null 2>&1
447 then
448 /etc/rc.d/$1 restart
449 fi'
450 elif [ -d /etc/dinit.d ] && command -v dinitctl >/dev/null 2>&1; then
451 RESTARTCMD='dinitctl --quiet restart --ignore-unstarted $1'
452 else
453 for x in /etc/init.d/rc.d /etc/rc.d /etc/init.d; do
454 [ -d $x ] || continue
455 RESTARTCMD="
456 if $x/\$1 $status >/dev/null 2>&1
457 then
458 $x/\$1 restart
459 fi"
460 break
461 done
462 fi
463
464 if [ -z "$RESTARTCMD" ]; then
465 if [ "$_NOINIT_WARNED" != true ]; then
466 warn "could not detect a useable init system"
467 _NOINIT_WARNED=true
468 fi
469 return 1
470 fi
471 _NOINIT_WARNED=
472 return 0
473 }
474
475 echo_resolv()
476 {
477 OIFS="$IFS"
478
479 [ -n "$1" ] && [ -f "$KEYDIR/$1" ] || return 1
480 echo "# resolv.conf from $1"
481 # Our variable maker works of the fact each resolv.conf per key
482 # is separated by blank lines.
483 # So we remove them when echoing them.
484 while read -r line; do
485 IFS="$OIFS"
486 if [ -n "$line" ]; then
487 # We need to set IFS here to preserve any whitespace
488 IFS=''
489 printf "%s\n" "$line"
490 fi
491 done < "$KEYDIR/$1"
492 IFS="$OIFS"
493 }
494
495 deprecated_key()
496 {
497 [ -d "$DEPRECATEDDIR" ] || return 1
498
499 cd "$DEPRECATEDDIR"
500 for da; do
501 for daf in *; do
502 [ -f "$daf" ] || continue
503 case "$da" in
504 $daf) return 0;;
505 esac
506 done
507 done
508 return 1
509 }
510
511 match()
512 {
513 match="$1"
514 file="$2"
515 retval=1
516 count=0
517
518 while read -r keyword value; do
519 new_match=
520 for om in $match; do
521 m="$om"
522 keep=
523 while [ -n "$m" ]; do
524 k="${m%%/*}"
525 r="${m#*/}"
526 f="${r%%/*}"
527 r="${r#*/}"
528 # If the length of m is the same as k/f then
529 # we know that we are done
530 if [ ${#m} = $((${#k} + 1 + ${#f})) ]; then
531 r=
532 fi
533 m="$r"
534 matched=false
535 case "$keyword" in
536 $k)
537 case "$value" in
538 $f)
539 matched=true
540 ;;
541 esac
542 ;;
543 esac
544 if ! $matched; then
545 keep="$keep${keep:+/}$k/$f"
546 fi
547 done
548 if [ -n "$om" ] && [ -z "$keep" ]; then
549 retval=0
550 break 2
551 fi
552 new_match="${new_match}${new_match:+ }${keep}"
553 done
554 match="${new_match}"
555 done < "$file"
556 return $retval
557 }
558
559 list_keys() {
560 list_cmd="$1"
561 shift
562
563 [ -d "$KEYDIR" ] || return 0
564 cd "$KEYDIR"
565
566 [ -n "$1" ] || set -- "*"
567 list=
568 retval=0
569 if [ "$list_cmd" = -i ] || [ "$list_cmd" = -l ]; then
570 for i in $@; do
571 if [ ! -f "$i" ]; then
572 if ! $force && [ "$i" != "*" ]; then
573 echo "No resolv.conf for key $i" >&2
574 fi
575 retval=2
576 continue
577 fi
578 list="$list $i"
579 done
580 [ -z "$list" ] || uniqify $list
581 return $retval
582 fi
583
584 if [ "$list_cmd" != -I ] && [ "$list_cmd" != -L ]; then
585 echo "list_keys: unknown command $list_cmd" >&2
586 return 1
587 fi
588
589 if [ -d "$EXCLUSIVEDIR" ]; then
590 cd "$EXCLUSIVEDIR"
591 for i in $EXCLUSIVEDIR/*; do
592 if [ -f "$i" ]; then
593 cd "$KEYDIR"
594 for ii in $inclusive_keys; do
595 if [ -f "$ii" ] && [ "${i#* }" = "$ii" ]; then
596 continue 2
597 fi
598 done
599 list="${i#* }"
600 break
601 fi
602 done
603 cd "$KEYDIR"
604 if [ -n "$list" ]; then
605 for i in $@; do
606 # list will be one item due to the above
607 if [ -f "$i" ] && [ "$i" = "$list" ]; then
608 echo "$i"
609 return 0
610 fi
611 done
612 return 0
613 fi
614 fi
615
616 for i in $key_order; do
617 for ii in "$i" "$i":* "$i".*; do
618 [ -f "$ii" ] && list="$list $ii"
619 done
620 done
621
622 for i in $dynamic_order; do
623 for ii in "$i" "$i":* "$i".*; do
624 if [ -f "$ii" ] && ! [ -e "$METRICDIR/"*" $ii" ]
625 then
626 list="$list $ii"
627 fi
628 done
629 done
630
631 # Interfaces have an implicit metric of 0 if not specified.
632 for i in *; do
633 if [ -f "$i" ] && ! [ -e "$METRICDIR/"*" $i" ]; then
634 list="$list $i"
635 fi
636 done
637
638 if [ -d "$METRICDIR" ]; then
639 cd "$METRICDIR"
640 for i in *; do
641 [ -f "$i" ] && list="$list ${i#* }"
642 done
643 cd "$KEYDIR"
644 fi
645
646 # Move deprecated keys to the back
647 active=
648 deprecated=
649 for i in $list; do
650 if deprecated_key "$i"; then
651 deprecated="$deprecated $i"
652 else
653 active="$active $i"
654 fi
655 done
656 list="$active $deprecated"
657
658 retval=0
659 if [ "$1" != "*" ]; then
660 cd "$KEYDIR"
661 matched=
662 for i in $@; do
663 if ! [ -f "$i" ]; then
664 if ! $force; then
665 echo "No resolv.conf for key $i" >&2
666 fi
667 retval=2
668 continue
669 fi
670 for ii in $list; do
671 if [ "$i" = "$ii" ]; then
672 matched="$matched${matched:+ }$i"
673 break
674 fi
675 done
676 done
677 if [ -z "$matched" ]; then
678 return $retval
679 fi
680 list="$matched"
681 fi
682
683 allowed=
684 for i in $(uniqify $list); do
685 if [ -n "$allow_keys" ]; then
686 x=false
687 for ii in $allow_keys; do
688 if [ "$i" = "$ii" ]; then
689 x=true
690 break
691 fi
692 done
693 $x || continue
694 fi
695 for ii in $deny_keys; do
696 if [ "$i" = "$ii" ]; then
697 continue 2
698 fi
699 done
700
701 if [ -n "$exclude" ] && match "$exclude" "$i"; then
702 continue
703 fi
704 allowed="$allowed${allowed:+ }$i"
705 done
706
707 cd "$KEYDIR"
708 for i in $exclusive_keys; do
709 for ii in $allowed; do
710 if [ "$i" = "$ii" ]; then
711 echo "$i"
712 return
713 fi
714 done
715 done
716 [ -z "$allowed" ] || echo "$allowed"
717 }
718
719 list_resolv()
720 {
721 keys="$(list_keys "$@")"
722 retval=$?
723 if [ "$retval" != 0 ]; then
724 return $retval
725 fi
726 for i in $keys; do
727 echo_resolv "$i" && echo
728 done
729 }
730
731 list_private()
732 {
733 KEYS=
734 cd "$KEYDIR"
735 if [ -z "$1" ]; then
736 set -- "*"
737 fi
738 for i in $@; do
739 if private_key "$i"; then
740 KEYS="${KEYS}${KEYS:+ }$i"
741 fi
742 done
743 if [ -n "$KEYS" ]; then
744 echo "$KEYS"
745 fi
746 }
747
748 list_nosearch()
749 {
750
751 KEYS=
752 cd "$KEYDIR"
753 if [ -z "$1" ]; then
754 set -- "*"
755 fi
756 for i in $@; do
757 if nosearch_key "$i"; then
758 KEYS="${KEYS}${KEYS:+ }$i"
759 fi
760 done
761 if [ -n "$KEYS" ]; then
762 echo "$KEYS"
763 fi
764 }
765
766 list_exclusive()
767 {
768 KEYS=
769 cd "$KEYDIR"
770 if [ -z "$1" ]; then
771 set -- "*"
772 fi
773 for i in $@; do
774 if exclusive_key "$i"; then
775 KEYS="${KEYS}${KEYS:+ }$i"
776 fi
777 done
778 if [ -n "$KEYS" ]; then
779 echo "$KEYS"
780 fi
781 }
782
783 list_remove()
784 {
785 [ -z "$2" ] && return 0
786 eval list=\"\$$1\"
787 shift
788 result=
789 retval=0
790
791 set -f
792 for e; do
793 found=false
794 for l in $list; do
795 case "$e" in
796 $l) found=true;;
797 esac
798 $found && break
799 done
800 if $found; then
801 retval=$(($retval + 1))
802 else
803 result="$result $e"
804 fi
805 done
806 set +f
807 echo "${result# *}"
808 return $retval
809 }
810
811 echo_prepend()
812 {
813 echo "# Generated by resolvconf"
814 if [ -n "$search_domains" ]; then
815 echo "search $search_domains"
816 fi
817 for n in $name_servers; do
818 echo "nameserver $n"
819 done
820 echo
821 }
822
823 echo_append()
824 {
825 echo "# Generated by resolvconf"
826 if [ -n "$search_domains_append" ]; then
827 echo "search $search_domains_append"
828 fi
829 for n in $name_servers_append; do
830 echo "nameserver $n"
831 done
832 echo
833 }
834
835 tolower() {
836 # There is no good way of doing this portably in shell :(
837 # Luckily we are only doing this for domain names which we
838 # know have to be ASCII.
839 # Non ASCII domains *should* be translated to ASCII *before*
840 # we get to this stage.
841 # We could use echo "$@" | tr '[:upper:]' '[:lower:]' but
842 # tr is in /usr/bin and may not be available when data is fed
843 # to resolvconf.
844 # So it's the cost of a pipe + fork vs this slow loop
845 #
846 for word; do
847 # Check if we have any upper to avoid looping per char
848 case "$word" in
849 *[A-Z]*) ;;
850 *) printf "%s " "$word"; continue;;
851 esac
852
853 while [ -n "$word" ]; do
854 # Remove everything except the first character
855 afterchar="${word#?}"
856 # Remove the afterchar to get the first character
857 char="${word%%$afterchar}"
858 # Assign afterchar back to word for looping
859 word="$afterchar"
860
861 # Now enforce lowercase a-z
862 case "$char" in
863 A) char=a;;
864 B) char=b;;
865 C) char=c;;
866 D) char=d;;
867 E) char=e;;
868 F) char=f;;
869 G) char=g;;
870 H) char=h;;
871 I) char=i;;
872 J) char=j;;
873 K) char=k;;
874 L) char=l;;
875 M) char=m;;
876 N) char=n;;
877 O) char=o;;
878 P) char=p;;
879 Q) char=q;;
880 R) char=r;;
881 S) char=s;;
882 T) char=t;;
883 U) char=u;;
884 V) char=v;;
885 W) char=w;;
886 X) char=x;;
887 Y) char=y;;
888 Z) char=z;;
889 esac
890 printf %s "$char"
891 done
892 printf " "
893 done
894 printf "\n"
895 }
896
897 # Strip any trailing dot from each name as a FQDN does not belong
898 # in resolv.conf(5).
899 # While DNS is not case sensitive, our labels for building the zones
900 # are, so ensure it's lower case.
901 process_domain()
902 {
903 for word in $(tolower "$@"); do
904 printf "%s " "${word%.}"
905 done
906 printf "\n"
907 }
908
909 process_resolv()
910 {
911 while read -r keyword value; do
912 for r in $replace; do
913 k="${r%%/*}"
914 r="${r#*/}"
915 f="${r%%/*}"
916 r="${r#*/}"
917 v="${r%%/*}"
918 case "$keyword" in
919 $k)
920 case "$value" in
921 $f) value="$v";;
922 esac
923 ;;
924 esac
925 done
926 val=
927 for sub in $value; do
928 for r in $replace_sub; do
929 k="${r%%/*}"
930 r="${r#*/}"
931 f="${r%%/*}"
932 r="${r#*/}"
933 v="${r%%/*}"
934 case "$keyword" in
935 $k)
936 case "$sub" in
937 $f) sub="$v";;
938 esac
939 ;;
940 esac
941 done
942 val="$val${val:+ }$sub"
943 done
944 case "$keyword" in
945 \#)
946 case "$val" in
947 "resolv.conf from "*) ;;
948 *) continue;;
949 esac
950 ;;
951 \#*) continue;;
952 esac
953 case "$keyword" in
954 domain|search) val="$(process_domain $val)";;
955 esac
956 printf "%s %s\n" "$keyword" "$val"
957 done
958 }
959
960 make_vars()
961 {
962 # Clear variables
963 DOMAIN=
964 DOMAINS=
965 SEARCH=
966 NAMESERVERS=
967 LOCALNAMESERVERS=
968
969 if [ -n "${name_servers}${search_domains}" ]; then
970 eval "$(echo_prepend | parse_resolv)"
971 fi
972 if [ -z "$VFLAG" ]; then
973 eval "$(list_resolv -L "$@" | process_resolv | parse_resolv)"
974 fi
975 if [ -n "${name_servers_append}${search_domains_append}" ]; then
976 eval "$(echo_append | parse_resolv)"
977 fi
978
979 # Ensure that we only list each domain once
980 newdomains=
981 for d in $DOMAINS; do
982 dn="${d%%:*}"
983 list_remove domain_blacklist "$dn" >/dev/null || continue
984 case " $newdomains" in
985 *" ${dn}:"*) continue;;
986 esac
987 newns=
988 for nd in $DOMAINS; do
989 if [ "$dn" = "${nd%%:*}" ]; then
990 ns="${nd#*:}"
991 while [ -n "$ns" ]; do
992 case ",$newns," in
993 *,${ns%%,*},*) ;;
994 *) list_remove name_server_blacklist \
995 "${ns%%,*}" >/dev/null \
996 && newns="$newns${newns:+,}${ns%%,*}";;
997 esac
998 [ "$ns" = "${ns#*,}" ] && break
999 ns="${ns#*,}"
1000 done
1001 fi
1002 done
1003 if [ -n "$newns" ]; then
1004 newdomains="$newdomains${newdomains:+ }$dn:$newns"
1005 fi
1006 done
1007 DOMAIN="$(list_remove domain_blacklist $DOMAIN)"
1008 SEARCH="$(uniqify $SEARCH)"
1009 SEARCH="$(list_remove domain_blacklist $SEARCH)"
1010 NAMESERVERS="$(uniqify $NAMESERVERS)"
1011 NAMESERVERS="$(list_remove name_server_blacklist $NAMESERVERS)"
1012 LOCALNAMESERVERS="$(uniqify $LOCALNAMESERVERS)"
1013 LOCALNAMESERVERS="$(list_remove name_server_blacklist $LOCALNAMESERVERS)"
1014 echo "DOMAIN='$DOMAIN'"
1015 echo "SEARCH='$SEARCH'"
1016 echo "NAMESERVERS='$NAMESERVERS'"
1017 echo "LOCALNAMESERVERS='$LOCALNAMESERVERS'"
1018 echo "DOMAINS='$newdomains'"
1019 }
1020
1021 force=false
1022 LFLAG=
1023 VFLAG=
1024 while getopts a:C:c:Dd:fhIiLlm:pRruvVx OPT; do
1025 case "$OPT" in
1026 f) force=true;;
1027 h) usage;;
1028 m) IF_METRIC="$OPTARG";;
1029 p)
1030 if [ "$IF_PRIVATE" = 1 ]; then
1031 IF_NOSEARCH=1
1032 else
1033 IF_PRIVATE=1
1034 fi
1035 ;;
1036 V)
1037 VFLAG=1
1038 if [ "$local_nameservers" = \
1039 "127.* 0.0.0.0 255.255.255.255 ::1" ]
1040 then
1041 local_nameservers=
1042 fi
1043 ;;
1044 x) IF_EXCLUSIVE=1;;
1045 '?') exit 1;;
1046 *)
1047 [ "$OPT" != L ] || LFLAG=1
1048 cmd="$OPT"; key="$OPTARG";;
1049 esac
1050 done
1051 shift $(($OPTIND - 1))
1052 if [ -n "$key" ]; then
1053 set -- "$key" "$@"
1054 fi
1055
1056 if [ -z "$cmd" ]; then
1057 if [ "$IF_PRIVATE" = 1 ]; then
1058 cmd=p
1059 elif [ "$IF_EXCLUSIVE" = 1 ]; then
1060 cmd=x
1061 fi
1062 fi
1063
1064 # -D ensures that the listed config file base dirs exist
1065 if [ "$cmd" = D ]; then
1066 config_mkdirs "$@"
1067 exit $?
1068 fi
1069
1070 # -i lists which keys have a resolv file
1071 if [ "$cmd" = i ]; then
1072 # If the -L modifier is given, the list is post-processed
1073 if [ "$LFLAG" = 1 ]; then
1074 cmd="L"
1075 fi
1076 list_keys "-$cmd" "$@"
1077 exit $?
1078 fi
1079
1080 # -l lists our resolv files, optionally for a specific key
1081 if [ "$cmd" = l ]; then
1082 list_resolv "-$cmd" "$@"
1083 exit $?
1084 fi
1085 # -L is the same as -l, but post-processed from our config
1086 if [ "$cmd" = L ]; then
1087 list_resolv "-$cmd" "$@" | process_resolv
1088 exit $?
1089 fi
1090
1091 if [ "$cmd" = p ]; then
1092 if [ "$IF_NOSEARCH" = 1 ]; then
1093 list_nosearch "$@"
1094 else
1095 list_private "$@"
1096 fi
1097 exit $?
1098 fi
1099
1100 if [ "$cmd" = x ]; then
1101 list_exclusive "$@"
1102 exit $?
1103 fi
1104
1105 # Restart a service or echo the command to restart a service
1106 if [ "$cmd" = r ] || [ "$cmd" = R ]; then
1107 detect_init || exit 1
1108 if [ "$cmd" = r ]; then
1109 eval "$RESTARTCMD"
1110 else
1111 echo "$RESTARTCMD" |
1112 sed -e '/^$/d' -e 's/^ //g'
1113 fi
1114 exit $?
1115 fi
1116
1117 # Not normally needed, but subscribers should be able to run independently
1118 if [ "$cmd" = v ] || [ -n "$VFLAG" ]; then
1119 make_vars "$@"
1120 exit $?
1121 fi
1122
1123 # Test that we have valid options
1124 case "$cmd" in
1125 a|d|C|c)
1126 if [ -z "$key" ]; then
1127 error_exit "Key not specified"
1128 fi
1129 ;;
1130 I|u) ;;
1131 *)
1132 if [ -n "$cmd" ] && [ "$cmd" != h ]; then
1133 error_exit "Unknown option $cmd"
1134 fi
1135 usage
1136 ;;
1137 esac
1138
1139 if [ "$cmd" = a ]; then
1140 for x in '/' \\ ' ' '*'; do
1141 case "$iface" in
1142 *[$x]*) error_exit "$x not allowed in key name";;
1143 esac
1144 done
1145 for x in '.' '-' '~'; do
1146 case "$iface" in
1147 [$x]*) error_exit \
1148 "$x not allowed at start of key name";;
1149 esac
1150 done
1151 [ "$cmd" = a ] && [ -t 0 ] && error_exit "No file given via stdin"
1152 fi
1153
1154 if [ ! -d "$VARDIR" ]; then
1155 if [ -L "$VARDIR" ]; then
1156 dir="$(readlink "$VARDIR")"
1157 # link maybe relative
1158 cd "${VARDIR%/*}"
1159 if ! mkdir -m 0755 -p "$dir"; then
1160 error_exit "Failed to create needed" \
1161 "directory $dir"
1162 fi
1163 else
1164 if ! mkdir -m 0755 -p "$VARDIR"; then
1165 error_exit "Failed to create needed" \
1166 "directory $VARDIR"
1167 fi
1168 fi
1169 fi
1170
1171 if [ ! -d "$KEYDIR" ]; then
1172 mkdir -m 0755 -p "$KEYDIR" || \
1173 error_exit "Failed to create needed directory $KEYDIR"
1174 if [ "$cmd" = d ]; then
1175 # Provide the same error messages as below
1176 if ! ${force}; then
1177 cd "$KEYDIR"
1178 for i in $@; do
1179 warn "No resolv.conf for key $i"
1180 done
1181 fi
1182 ${force}
1183 exit $?
1184 fi
1185 fi
1186
1187 # A key was added, changed, deleted or a general update was called.
1188 # Due to exclusivity we need to ensure that this is an atomic operation.
1189 # Our subscribers *may* need this as well if the init system is sub par.
1190 # As such we spinlock at this point as best we can.
1191 # We don't use flock(1) because it's not widely available and normally resides
1192 # in /usr which we do our very best to operate without.
1193 [ -w "$VARDIR" ] || error_exit "Cannot write to $LOCKDIR"
1194 : ${lock_timeout:=10}
1195 : ${clear_nopids:=5}
1196 have_pid=false
1197 had_pid=false
1198 while true; do
1199 if mkdir "$LOCKDIR" 2>/dev/null; then
1200 trap 'rm -rf "$LOCKDIR";' EXIT
1201 trap 'rm -rf "$LOCKDIR"; exit 1' INT QUIT ABRT SEGV ALRM TERM
1202 echo $$ >"$LOCKDIR/pid"
1203 break
1204 fi
1205 pid=$(cat "$LOCKDIR/pid" 2>/dev/null)
1206 if [ "$pid" -gt 0 ] 2>/dev/null; then
1207 have_pid=true
1208 had_pid=true
1209 else
1210 have_pid=false
1211 clear_nopids=$(($clear_nopids - 1))
1212 if [ "$clear_nopids" -le 0 ]; then
1213 warn "not seen a pid, clearing lock directory"
1214 rm -rf "$LOCKDIR"
1215 else
1216 lock_timeout=$(($lock_timeout - 1))
1217 sleep 1
1218 fi
1219 continue
1220 fi
1221 if $have_pid && ! kill -0 "$pid"; then
1222 warn "clearing stale lock pid $pid"
1223 rm -rf "$LOCKDIR"
1224 continue
1225 fi
1226 lock_timeout=$(($lock_timeout - 1))
1227 if [ "$lock_timeout" -le 0 ]; then
1228 if $have_pid; then
1229 error_exit "timed out waiting for lock from pid $pid"
1230 else
1231 if $had_pid; then
1232 error_exit "timed out waiting for lock" \
1233 "from some pids"
1234 else
1235 error_exit "timed out waiting for lock"
1236 fi
1237 fi
1238 fi
1239 sleep 1
1240 done
1241 unset have_pid had_pid clear_nopids
1242
1243 case "$cmd" in
1244 a)
1245 # Read resolv.conf from stdin
1246 resolv="$(cat)"
1247 changed=false
1248 changedfile=false
1249 # If what we are given matches what we have, then do nothing
1250 if [ -e "$KEYDIR/$key" ]; then
1251 if [ "$(echo "$resolv")" != \
1252 "$(cat "$KEYDIR/$key")" ]
1253 then
1254 changed=true
1255 changedfile=true
1256 fi
1257 else
1258 changed=true
1259 changedfile=true
1260 fi
1261
1262 # Set metric and private before creating the resolv.conf file
1263 # to ensure that it will have the correct flags
1264 [ ! -d "$METRICDIR" ] && mkdir "$METRICDIR"
1265 oldmetric="$METRICDIR/"*" $key"
1266 newmetric=
1267 if [ -n "$IF_METRIC" ]; then
1268 # Pad metric to 6 characters, so 5 is less than 10
1269 while [ ${#IF_METRIC} -le 6 ]; do
1270 IF_METRIC="0$IF_METRIC"
1271 done
1272 newmetric="$METRICDIR/$IF_METRIC $key"
1273 fi
1274 rm -f "$METRICDIR/"*" $key"
1275 [ "$oldmetric" != "$newmetric" ] &&
1276 [ "$oldmetric" != "$METRICDIR/* $key" ] &&
1277 changed=true
1278 [ -n "$newmetric" ] && echo " " >"$newmetric"
1279
1280 case "$IF_PRIVATE" in
1281 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
1282 if [ ! -d "$PRIVATEDIR" ]; then
1283 [ -e "$PRIVATEDIR" ] && rm "$PRIVATEDIR"
1284 mkdir "$PRIVATEDIR"
1285 fi
1286 [ -e "$PRIVATEDIR/$key" ] || changed=true
1287 [ -d "$PRIVATEDIR" ] && echo " " >"$PRIVATEDIR/$key"
1288 ;;
1289 *)
1290 if [ -e "$PRIVATEDIR/$key" ]; then
1291 rm -f "$PRIVATEDIR/$key"
1292 changed=true
1293 fi
1294 ;;
1295 esac
1296
1297 case "$IF_NOSEARCH" in
1298 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
1299 if [ ! -d "$NOSEARCHDIR" ]; then
1300 [ -e "$NOSEARCHDIR" ] && rm "$NOSEARCHDIR"
1301 mkdir "$NOSEARCHDIR"
1302 fi
1303 [ -e "$NOSEARCHDIR/$key" ] || changed=true
1304 [ -d "$NOSEARCHDIR" ] && echo " " >"$NOSEARCHDIR/$key"
1305 ;;
1306 *)
1307 if [ -e "$NOSEARCHDIR/$key" ]; then
1308 rm -f "$NOSEARCHDIR/$key"
1309 changed=true
1310 fi
1311 ;;
1312 esac
1313 set +x
1314
1315 oldexcl=
1316 for x in "$EXCLUSIVEDIR/"*" $key"; do
1317 if [ -f "$x" ]; then
1318 oldexcl="$x"
1319 break
1320 fi
1321 done
1322 case "$IF_EXCLUSIVE" in
1323 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
1324 if [ ! -d "$EXCLUSIVEDIR" ]; then
1325 [ -e "$EXCLUSIVEDIR" ] && rm "$EXCLUSIVEDIR"
1326 mkdir "$EXCLUSIVEDIR"
1327 fi
1328 cd "$EXCLUSIVEDIR"
1329 for x in *; do
1330 [ -f "$x" ] && break
1331 done
1332 if [ "${x#* }" != "$key" ]; then
1333 if [ "$x" = "${x% *}" ]; then
1334 x=10000000
1335 else
1336 x="${x% *}"
1337 fi
1338 if [ "$x" = "0000000" ]; then
1339 warn "exclusive underflow"
1340 else
1341 x=$(($x - 1))
1342 fi
1343 if [ -d "$EXCLUSIVEDIR" ]; then
1344 echo " " >"$EXCLUSIVEDIR/$x $key"
1345 fi
1346 changed=true
1347 fi
1348 ;;
1349 *)
1350 if [ -f "$oldexcl" ]; then
1351 rm -f "$oldexcl"
1352 changed=true
1353 fi
1354 ;;
1355 esac
1356
1357 if $changedfile; then
1358 printf "%s\n" "$resolv" >"$KEYDIR/$key" || exit $?
1359 elif ! $changed && [ ! -e "$VARDIR"/error ]; then
1360 exit 0
1361 fi
1362 unset changed changedfile oldmetric newmetric x oldexcl
1363 ;;
1364
1365 d)
1366 # Delete any existing information about the key
1367 cd "$KEYDIR"
1368 changed=false
1369 for i in $@; do
1370 if [ -e "$i" ]; then
1371 changed=true
1372 elif ! ${force}; then
1373 warn "No resolv.conf for key $i"
1374 fi
1375 rm -f "$i" "$METRICDIR/"*" $i" \
1376 "$PRIVATEDIR/$i" \
1377 "$EXCLUSIVEDIR/"*" $i" || exit $?
1378 done
1379
1380 if ! $changed && [ ! -e "$VARDIR"/error ]; then
1381 # Set the return code based on the forced flag
1382 $force
1383 exit $?
1384 fi
1385 unset changed i
1386 ;;
1387
1388 C)
1389 # Mark key as deprecated
1390 [ ! -d "$DEPRECATEDDIR" ] && mkdir "$DEPRECATEDDIR"
1391 cd "$DEPRECATEDDIR"
1392 changed=false
1393 for i in $@; do
1394 if [ ! -e "$i" ]; then
1395 changed=true
1396 echo " " >"$i" || exit $?
1397 fi
1398 done
1399 if ! $changed && [ ! -e "$VARDIR"/error ]; then
1400 exit 0
1401 fi
1402 unset changed i
1403 ;;
1404
1405 c)
1406 # Mark key as active
1407 if [ -d "$DEPRECATEDDIR" ]; then
1408 cd "$DEPRECATEDDIR"
1409 changed=false
1410 for i in $@; do
1411 if [ -e "$i" ]; then
1412 changed=true
1413 rm "$i" || exit $?
1414 fi
1415 done
1416 if ! $changed && [ ! -e "$VARDIR"/error ]; then
1417 exit 0
1418 fi
1419 unset changed i
1420 fi
1421 ;;
1422 I)
1423 # Init the state dir, keeping our lock and key directories only
1424 for i in "$VARDIR"/*; do
1425 case "$i" in
1426 "$LOCKDIR") ;;
1427 "$KEYDIR") rm -rf "$KEYDIR"/*;;
1428 *) rm -rf "$i";;
1429 esac
1430 done
1431 ;;
1432 esac
1433
1434 case "${resolvconf:-YES}" in
1435 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
1436 *) exit 0;;
1437 esac
1438
1439 # Try and detect a suitable init system for our scripts
1440 detect_init
1441 export RESTARTCMD RCDIR _NOINIT_WARNED
1442
1443 eval "$(make_vars)"
1444 export RESOLVCONF DOMAINS SEARCH NAMESERVERS LOCALNAMESERVERS
1445 : ${list_resolv:=list_resolv -L}
1446 retval=0
1447
1448 # Run scripts in the same directory resolvconf is run from
1449 # in case any scripts accidentally dump files in the wrong place.
1450 cd "$_PWD"
1451 for script in "$LIBEXECDIR"/*; do
1452 if [ -f "$script" ]; then
1453 script_var="${script##*/}"
1454 while [ "${script_var%%-*}" != "$script_var" ]; do
1455 script_var="${script_var%%-*}_${script_var#*-}"
1456 done
1457 eval script_enabled="\$$script_var"
1458 case "${script_enabled:-YES}" in
1459 [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
1460 *) continue;;
1461 esac
1462 if [ -x "$script" ]; then
1463 "$script" "$cmd" "$key"
1464 else
1465 (set -- "$cmd" "$key"; . "$script")
1466 fi
1467 retval=$(($retval + $?))
1468 fi
1469 done
1470 if [ "$retval" = 0 ]; then
1471 rm -f "$VARDIR"/error
1472 else
1473 echo "$retval" >"$VARDIR"/error
1474 fi
1475 exit $retval
1476