1#!/bin/sh
2# Copyright (c) 2007-2025 Roy Marples
3# All rights reserved
4
5# libc subscriber for resolvconf
6
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10#     * Redistributions of source code must retain the above copyright
11#       notice, this list of conditions and the following disclaimer.
12#     * Redistributions in binary form must reproduce the above
13#       copyright notice, this list of conditions and the following
14#       disclaimer in the documentation and/or other materials provided
15#       with the distribution.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29SYSCONFDIR=@SYSCONFDIR@
30LIBEXECDIR=@LIBEXECDIR@
31VARDIR=@VARDIR@
32IFACEDIR="$VARDIR/interfaces"
33NL="
34"
35
36# sed may not be available, and this is faster on small files
37key_get_value()
38{
39          key="$1"
40          shift
41
42          if [ $# -eq 0 ]; then
43                    while read -r line; do
44                              case "$line" in
45                              "$key"*) echo "${line##$key}";;
46                              esac
47                    done
48          else
49                    for x do
50                              while read -r line; do
51                                        case "$line" in
52                                        "$key"*) echo "${line##$key}";;
53                                        esac
54                              done < "$x"
55                    done
56          fi
57}
58
59keys_remove()
60{
61          while read -r line; do
62                    found=false
63                    for key do
64                              case "$line" in
65                              "$key"*|"#"*|" "*|" "*|"") found=true;;
66                              esac
67                              $found && break
68                    done
69                    $found || echo "$line"
70          done
71}
72
73local_nameservers="127.* 0.0.0.0 255.255.255.255 ::1"
74
75# Support original resolvconf configuration layout
76# as well as the openresolv config file
77if [ -f "$SYSCONFDIR"/resolvconf.conf ]; then
78          . "$SYSCONFDIR"/resolvconf.conf
79elif [ -d "$SYSCONFDIR"/resolvconf ]; then
80          SYSCONFDIR="$SYSCONFDIR/resolvconf"
81          base="$SYSCONFDIR/resolv.conf.d/base"
82          if [ -f "$base" ]; then
83                    prepend_nameservers="$(key_get_value "nameserver " "$base")"
84                    domain="$(key_get_value "domain " "$base")"
85                    prepend_search="$(key_get_value "search " "$base")"
86                    resolv_conf_options="$(key_get_value "options " "$base")"
87                    resolv_conf_sortlist="$(key_get_value "sortlist " "$base")"
88          fi
89          if [ -f "$SYSCONFDIR"/resolv.conf.d/head ]; then
90                    resolv_conf_head="$(cat "${SYSCONFDIR}"/resolv.conf.d/head)"
91          fi
92          if [ -f "$SYSCONFDIR"/resolv.conf.d/tail ]; then
93                    resolv_conf_tail="$(cat "$SYSCONFDIR"/resolv.conf.d/tail)"
94          fi
95fi
96: ${resolv_conf:=/etc/resolv.conf}
97: ${resolv_conf_tmp:="$resolv_conf.$$.openresolv"}
98: ${libc_service:=nscd}
99: ${list_resolv:=@SBINDIR@/resolvconf -L}
100if [ "${resolv_conf_head-x}" = x ] && [ -f "$SYSCONFDIR"/resolv.conf.head ]
101then
102          resolv_conf_head="$(cat "${SYSCONFDIR}"/resolv.conf.head)"
103fi
104if [ "${resolv_conf_tail-x}" = x ] && [ -f "$SYSCONFDIR"/resolv.conf.tail ]
105then
106          resolv_conf_tail="$(cat "$SYSCONFDIR"/resolv.conf.tail)"
107fi
108
109backup=true
110signature="# Generated by resolvconf"
111
112uniqify()
113{
114          result=
115          while [ -n "$1" ]; do
116                    case " $result " in
117                    *" $1 "*);;
118                    *) result="$result $1";;
119                    esac
120                    shift
121          done
122          echo "${result# *}"
123}
124
125case "${resolv_conf_passthrough:-NO}" in
126[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
127          backup=false
128          newest=
129          for conf in "$IFACEDIR"/*; do
130                    if [ -z "$newest" ] || [ "$conf" -nt "$newest" ]; then
131                              newest="$conf"
132                    fi
133          done
134          [ -z "$newest" ] && exit 0
135          newconf="$(cat "$newest")$NL"
136          ;;
137/dev/null|[Nn][Uu][Ll][Ll])
138          : ${resolv_conf_local_only:=NO}
139          if [ "$local_nameservers" = "127.* 0.0.0.0 255.255.255.255 ::1" ]; then
140                    local_nameservers=
141          fi
142          # Need to overwrite our variables.
143          eval "$(@SBINDIR@/resolvconf -V)"
144          ;;
145
146*)
147          [ -z "$RESOLVCONF" ] && eval "$(@SBINDIR@/resolvconf -v)"
148          ;;
149esac
150case "${resolv_conf_passthrough:-NO}" in
151[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) ;;
152*)
153          : ${domain:=$DOMAIN}
154          newsearch="$(uniqify $prepend_search $SEARCH $append_search)"
155          NS="$LOCALNAMESERVERS $NAMESERVERS"
156          newns=
157          gotlocal=false
158          for n in $(uniqify $prepend_nameservers $NS $append_nameservers); do
159                    add=true
160                    islocal=false
161                    for l in $local_nameservers; do
162                              case "$n" in
163                              $l) islocal=true; gotlocal=true; break;;
164                              esac
165                    done
166                    if ! $islocal; then
167                              case "${resolv_conf_local_only:-YES}" in
168                              [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
169                                        $gotlocal && add=false;;
170                              esac
171                    fi
172                    $add && newns="$newns $n"
173          done
174
175          # Hold our new resolv.conf in a variable to save on temporary files
176          newconf="$signature$NL"
177          if [ -n "$resolv_conf_head" ]; then
178                    newconf="$newconf$resolv_conf_head$NL"
179          fi
180
181          [ -n "$domain" ] && newconf="${newconf}domain $domain$NL"
182          if [ -n "$newsearch" ] && [ "$newsearch" != "$domain" ]; then
183                    newconf="${newconf}search $newsearch$NL"
184          fi
185          for n in $newns; do
186                    newconf="${newconf}nameserver $n$NL"
187          done
188
189          # Now add anything we don't care about such as sortlist and options
190          stuff="$($list_resolv | keys_remove nameserver domain search)"
191          if [ -n "$stuff" ]; then
192                    newconf="$newconf$stuff$NL"
193          fi
194
195          # Append any user defined ones
196          if [ -n "$resolv_conf_options" ]; then
197                    newconf="${newconf}options $resolv_conf_options$NL"
198          fi
199          if [ -n "$resolv_conf_sortlist" ]; then
200                    newconf="${newconf}sortlist $resolv_conf_sortlist$NL"
201          fi
202
203          if [ -n "$resolv_conf_tail" ]; then
204                    newconf="$newconf$resolv_conf_tail$NL"
205          fi
206          ;;
207esac
208
209# Check if the file has actually changed or not
210if [ -e "$resolv_conf" ]; then
211          [ "$(cat "$resolv_conf")" = "$(printf %s "$newconf")" ] && exit 0
212fi
213
214# Change is good.
215# If the old file does not have our signature, back it up.
216# If the new file just has our signature, restore the backup.
217if $backup; then
218          if [ "$newconf" = "$signature$NL" ]; then
219                    if [ -e "$resolv_conf.bak" ]; then
220                              newconf="$(cat "$resolv_conf.bak")$NL"
221                    fi
222          elif [ -e "$resolv_conf" ]; then
223                    read line <"$resolv_conf"
224                    if [ "$line" != "$signature" ]; then
225                              cp "$resolv_conf" "$resolv_conf.bak"
226                    fi
227          fi
228fi
229
230# There are pros and cons for writing directly to resolv.conf
231# instead of a temporary file and then moving it over.
232# The default is to write to resolv.conf as it has the least
233# issues and has been the long standing default behaviour.
234case "${resolv_conf_mv:-NO}" in
235[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
236          # Protect against symlink attack, ensure new file does not exist
237          rm -f "$resolv_conf_tmp"
238          # Keep original file owner, group and mode
239          [ -r "$resolv_conf" ] && cp -p "$resolv_conf" "$resolv_conf_tmp"
240          # Create our resolv.conf now
241          if (umask 022; printf %s "$newconf" >"$resolv_conf_tmp"); then
242                    mv "$resolv_conf_tmp" "$resolv_conf"
243          fi
244          ;;
245*)
246          (umask 022; printf %s "$newconf" >"$resolv_conf")
247          ;;
248esac
249
250if [ -n "$libc_restart" ]; then
251          eval $libc_restart
252elif [ -n "$RESTARTCMD" ]; then
253          set -- ${libc_service}
254          eval "$RESTARTCMD"
255else
256          @SBINDIR@/resolvconf -r ${libc_service}
257fi
258
259retval=0
260# Notify users of the resolver
261for script in "$LIBEXECDIR"/libc.d/*; do
262          if [ -f "$script" ]; then
263                    if [ -x "$script" ]; then
264                              "$script" "$@"
265                    else
266                              (. "$script")
267                    fi
268                    retval=$(($retval + $?))
269          fi
270done
271exit $retval
272