]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - usr.sbin/bsdconfig/networking/share/resolv.subr
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / usr.sbin / bsdconfig / networking / share / resolv.subr
1 if [ ! "$_NETWORKING_RESOLV_SUBR" ]; then _NETWORKING_RESOLV_SUBR=1
2 #
3 # Copyright (c) 2006-2013 Devin Teske
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 # 1. Redistributions of source code must retain the above copyright
10 #    notice, this list of conditions and the following disclaimer.
11 # 2. Redistributions in binary form must reproduce the above copyright
12 #    notice, this list of conditions and the following disclaimer in the
13 #    documentation and/or other materials provided with the distribution.
14 #
15 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 # ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 # SUCH DAMAGE.
26 #
27 # $FreeBSD$
28 #
29 ############################################################ INCLUDES
30
31 BSDCFG_SHARE="/usr/share/bsdconfig"
32 . $BSDCFG_SHARE/common.subr || exit 1
33 f_dprintf "%s: loading includes..." networking/resolv.subr
34 f_include $BSDCFG_SHARE/dialog.subr
35 f_include $BSDCFG_SHARE/media/tcpip.subr
36 f_include $BSDCFG_SHARE/networking/common.subr
37 f_include $BSDCFG_SHARE/networking/ipaddr.subr
38 f_include $BSDCFG_SHARE/strings.subr
39
40 BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="120.networking"
41 f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr
42
43 ############################################################ CONFIGURATION
44
45 #
46 # When updating resolv.conf(5), should we populate the `search' directive with
47 # all possible sub-domains? In example, if the domain is "sub.domain.com", when
48 # the below option is set to 1, include both "sub.domain.com" and "domain.com"
49 # in the `search' directive, otherwise use only "sub.domain.com".
50 #
51 # When enabled (set to 1), specify the minimum number of dots required for each
52 # `search' domain by setting the second option below, `RESOLVER_SEARCH_NDOTS'.
53 #
54 : ${RESOLVER_SEARCH_DOMAINS_ALL:=1}
55 : ${RESOLVER_SEARCH_NDOTS:=1}
56
57 ############################################################ FUNCTIONS
58
59 # f_resolv_conf_domain
60 #
61 # Returns the domain configured in resolv.conf(5).
62 #
63 f_resolv_conf_domain()
64 {
65         tail -r "$RESOLV_CONF" 2> /dev/null | awk \
66         '
67                 BEGIN { found = 0 }
68                 ( tolower($1) == "domain" ) \
69                 {
70                         print $2
71                         found = 1
72                         exit
73                 }
74                 END { exit ! found }
75         '
76 }
77
78 # f_resolv_conf_search
79 #
80 # Returns the search configured in resolv.conf(5).
81 #
82 f_resolv_conf_search()
83 {
84         tail -r "$RESOLV_CONF" 2> /dev/null | awk \
85         '
86                 BEGIN { found = 0 }
87                 {
88                         tl0 = tolower($0)
89                         if ( match(tl0, /^[[:space:]]*search[[:space:]]+/) ) {
90                                 search = substr($0, RLENGTH + 1)
91                                 sub(/[[:space:]]*#.*$/, "", search)
92                                 gsub(/[[:space:]]+/, " ", search)
93                                 print search
94                                 found = 1
95                                 exit
96                         }
97                 }
98                 END { exit ! found }
99         '
100 }
101
102 # f_dialog_resolv_conf_update $hostname
103 #
104 # Updates the search/domain directives in resolv.conf(5) given a valid fully-
105 # qualified hostname.
106 #
107 # This function is a two-parter. Below is the awk(1) portion of the function,
108 # afterward is the sh(1) function which utilizes the below awk script.
109 #
110 f_dialog_resolv_conf_update_awk='
111 # Variables that should be defined on the invocation line:
112 #       -v domain="domain"
113 #       -v search_all="0|1"
114 #       -v search_ndots="1+"
115 #
116 BEGIN {
117         domain_found = search_found = 0
118
119         if ( search_all ) {
120                 search = ""
121                 subdomain = domain
122                 if ( search_ndots < 1 )
123                         search_ndots = 1
124
125                 ndots = split(subdomain, labels, ".") - 1
126                 while ( ndots-- >= search_ndots ) {
127                         if ( length(search) ) search = search " "
128                         search = search subdomain
129                         sub(/[^.]*\./, "", subdomain)
130                 }
131         }
132         else search = domain
133 }
134 {
135         if ( domain_found && search_found ) { print; next }
136
137         tl0 = tolower($0)
138         if ( ! domain_found && \
139              match(tl0, /^[[:space:]]*domain[[:space:]]+/) ) \
140         {
141                 if ( length(domain) ) {
142                         printf "%s%s\n", substr($0, 0, RLENGTH), domain
143                         domain_found = 1
144                 }
145         }
146         else if ( ! search_found && \
147                   match(tl0, /^[[:space:]]*search[[:space:]]+/) ) \
148         {
149                 if ( length(search) ) {
150                         printf "%s%s\n", substr($0, 0, RLENGTH), search
151                         search_found = 1
152                 }
153         }
154         else print
155 }
156 END {
157         if ( ! search_found && length(search) )
158                 printf "search\t%s\n", search
159         if ( ! domain_found && length(domain) )
160                 printf "domain\t%s\n", domain
161 }
162 '
163 f_dialog_resolv_conf_update()
164 {
165         local funcname=f_dialog_resolv_conf_update
166         local hostname="$1"
167
168         #
169         # Extrapolate the desired domain search parameter for resolv.conf(5)
170         #
171         local search nfields ndots domain="${hostname#*.}"
172         if [ "$RESOLVER_SEARCH_DOMAINS_ALL" = "1" ]; then
173                 search=
174                 IFS=. f_count_ifs nfields "$domain"
175                 ndots=$(( $nfields - 1 ))
176                 while [ $ndots -ge ${RESOLVER_SEARCH_NDOTS:-1} ]; do
177                         search="$search $domain"
178                         domain="${domain#*.}"
179                         ndots=$(( $ndots - 1 ))
180                 done
181                 search="${search# }"
182                 domain="${hostname#*.}"
183         else
184                 search="$domain"
185         fi
186
187         #
188         # Save domain/search information only if different from resolv.conf(5)
189         #
190         if [ "$domain" != "$( f_resolv_conf_domain )" -o \
191              "$search" != "$( f_resolv_conf_search )" ]
192         then
193                 f_dialog_info "Saving new domain/search settings" \
194                               "to resolv.conf(5)..."
195
196                 #
197                 # Create a new temporary file to write our resolv.conf(5)
198                 # update with our new `domain' and `search' directives.
199                 #
200                 local tmpfile
201                 f_eval_catch -dk tmpfile $funcname mktemp \
202                         'mktemp -t "%s"' "$tmpfile" || return $DIALOG_CANCEL
203
204                 #
205                 # Fixup permissions and ownership (mktemp(1) creates the
206                 # temporary file with 0600 permissions -- change the
207                 # permissions and ownership to match resolv.conf(5) before
208                 # we write it out and mv(1) it into place).
209                 #
210                 local mode owner
211                 f_eval_catch -dk mode $funcname stat \
212                         'stat -f "%%#Lp" "%s"' "$RESOLV_CONF" || mode=0644
213                 f_eval_catch -dk owner $funcname stat \
214                         'stat -f "%%u:%%g" "%s"' "$RESOLV_CONF" ||
215                         owner="root:wheel"
216                 f_eval_catch -d $funcname chmod \
217                         'chmod "%s" "%s"' "$mode" "$tmpfile"
218                 f_eval_catch -d $funcname chown \
219                         'chown "%s" "%s"' "$owner" "$tmpfile"
220
221                 #
222                 # Operate on resolv.conf(5), replacing only the last
223                 # occurrences of `domain' and `search' directives (or add
224                 # them to the top if not found), in strict-adherence to the
225                 # following entry in resolver(5):
226                 #
227                 #       The domain and search keywords are mutually exclusive.
228                 #       If more than one instance of these keywords is present,
229                 #       the last instance will override.
230                 #
231                 # NOTE: If RESOLVER_SEARCH_DOMAINS_ALL is set to `1' in the
232                 # environment, all sub-domains will be added to the `search'
233                 # directive, not just the FQDN.
234                 #
235                 local domain="${hostname#*.}" new_contents
236                 [ "$domain" = "$hostname" ] && domain=
237                 new_contents=$( tail -r "$RESOLV_CONF" 2> /dev/null )
238                 new_contents=$( echo "$new_contents" | awk \
239                         -v domain="$domain" \
240                         -v search_all="${RESOLVER_SEARCH_DOMAINS_ALL:-1}" \
241                         -v search_ndots="${RESOLVER_SEARCH_NDOTS:-1}" \
242                         "$f_dialog_resolv_conf_update_awk" )
243
244                 #
245                 # Write the temporary file contents and move the temporary
246                 # file into place.
247                 #
248                 echo "$new_contents" | tail -r > "$tmpfile" ||
249                         return $DIALOG_CANCEL
250                 f_eval_catch -d $funcname mv \
251                         'mv "%s" "%s"' "$tmpfile" "$RESOLV_CONF"
252
253         fi
254 }
255
256 # f_dialog_input_nameserver [ $n $nameserver ]
257 #
258 # Allows the user to edit a given nameserver. The first argument is the
259 # resolv.conf(5) nameserver ``instance'' integer. For example, this will be one
260 # if editing the first nameserver instance, two if editing the second, three if
261 # the third, ad nauseum. If this argument is zero, null, or missing, the value
262 # entered by the user (if non-null) will be added to resolv.conf(5) as a new
263 # `nameserver' entry. The second argument is the IPv4 address of the nameserver
264 # to be edited -- this will be displayed as the initial value during the edit.
265 #
266 # Taint-checking is performed when editing an existing entry (when the second
267 # argument is one or higher) in that the first argument must match the current
268 # value of the Nth `nameserver' instance in resolv.conf(5) else an error is
269 # generated discarding any/all changes.
270 #
271 # This function is a two-parter. Below is the awk(1) portion of the function,
272 # afterward is the sh(1) function which utilizes the below awk script.
273 #
274 f_dialog_input_nameserver_edit_awk='
275 # Variables that should be defined on the invocation line:
276 #       -v nsindex="1+"
277 #       -v old_value="..."
278 #       -v new_value="..."
279 #
280 BEGIN {
281         if ( nsindex < 1 ) exit 1
282         found = n = 0
283 }
284 {
285         if ( found ) { print; next }
286
287         if ( match(tolower($0), /^[[:space:]]*nameserver[[:space:]]+/)) {
288                 if ( ++n == nsindex ) {
289                         if ( $2 != old_value ) exit 2
290                         if ( new_value != "" ) printf "%s%s\n", \
291                                 substr($0, 0, RLENGTH), new_value
292                         found = 1
293                 }
294                 else print
295         }
296         else print
297 }
298 END { if ( ! found ) exit 3 }
299 '
300 f_dialog_input_nameserver()
301 {
302         local funcname=f_dialog_input_nameserver
303         local index="${1:-0}" old_ns="$2" new_ns
304         local ns="$old_ns"
305
306         #
307         # Perform sanity checks
308         #
309         f_isinteger "$index" || return $DIALOG_CANCEL
310         [ $index -ge 0 ] || return $DIALOG_CANCEL
311
312         local msg
313         if [ $index -gt 0 ]; then
314                 if [ "$USE_XDIALOG" ]; then
315                         msg="$xmsg_please_enter_nameserver_existing"
316                 else
317                         msg="$msg_please_enter_nameserver_existing"
318                 fi
319         else
320                 msg="$msg_please_enter_nameserver"
321         fi
322
323         #
324         # Loop until the user provides taint-free input.
325         #
326         while :; do
327                 f_dialog_input new_ns "$msg" "$ns" \
328                                "$hline_num_punc_tab_enter" || return $?
329
330                 # Take only the first "word" of the user's input
331                 new_ns="${new_ns%%[$IFS]*}"
332
333                 # Taint-check the user's input
334                 [ "$new_ns" ] || break
335                 f_dialog_validate_ipaddr "$new_ns" && break
336
337                 # Update prompt to allow user to re-edit previous entry
338                 ns="$new_ns"
339         done
340
341         #
342         # Save only if the user changed the nameserver.
343         #
344         if [ $index -eq "0" -a "$new_ns" ]; then
345                 f_dialog_info "$msg_saving_nameserver"
346                 printf "nameserver\t%s\n" "$new_ns" >> "$RESOLV_CONF"
347                 return $DIALOG_OK
348         elif [ $index -gt 0 -a "$old_ns" != "$new_ns" ]; then
349                 if [ "$new_ns" ]; then
350                         msg="$msg_saving_nameserver_existing"
351                 else
352                         msg="$msg_removing_nameserver"
353                 fi
354                 f_dialog_info "$msg"
355
356                 #
357                 # Create a new temporary file to write our new resolv.conf(5)
358                 #
359                 local tmpfile
360                 f_eval_catch -dk tmpfile $funcname mktemp \
361                         'mktemp -t "%s"' "$pgm" || return $DIALOG_CANCEL
362
363                 #
364                 # Quietly fixup permissions and ownership
365                 #
366                 local mode owner
367                 f_eval_catch -dk mode $funcname stat \
368                         'stat -f "%%#Lp" "%s"' "$RESOLV_CONF" || mode=0644
369                 f_eval_catch -dk owner $funcname stat \
370                         'stat -f "%%u:%%g" "%s"' "$RESOLV_CONF" ||
371                         owner="root:wheel"
372                 f_eval_catch -d $funcname chmod \
373                         'chmod "%s" "%s"' "$mode" "$tmpfile"
374                 f_eval_catch -d $funcname chown \
375                         'chown "%s" "%s"' "$owner" "$tmpfile"
376
377                 #
378                 # Operate on resolv.conf(5)
379                 #
380                 local new_contents
381                 new_contents=$( awk -v nsindex="$index"    \
382                                     -v old_value="$old_ns" \
383                                     -v new_value="$new_ns" \
384                                     "$f_dialog_input_nameserver_edit_awk" \
385                                     "$RESOLV_CONF" )
386
387                 #
388                 # Produce an appropriate error message if necessary.
389                 #
390                 local retval=$?
391                 case $retval in
392                 1) f_die 1 "$msg_internal_error_nsindex_value" "$nsindex" ;;
393                 2) f_show_msg "$msg_resolv_conf_changed_while_editing"
394                    return $retval ;;
395                 3) f_show_msg "$msg_resolv_conf_entry_no_longer_exists"
396                    return $retval ;;
397                 esac
398
399                 #
400                 # Write the temporary file contents and move the temporary
401                 # file into place.
402                 #
403                 echo "$new_contents" > "$tmpfile" || return $DIALOG_CANCEL
404                 f_eval_catch -d $funcname mv \
405                         'mv "%s" "%s"' "$tmpfile" "$RESOLV_CONF"
406         fi
407 }
408
409 # f_dialog_menu_nameservers
410 #
411 # Edit the nameservers in resolv.conf(5).
412 #
413 f_dialog_menu_nameservers()
414 {
415         local prompt="$msg_dns_configuration"
416         local menu_list # Calculated below
417         local hline="$hline_arrows_tab_enter"
418         local defaultitem=
419
420         local height width rows
421         local opt_exit="$msg_return_to_previous_menu"
422         local opt_add="$msg_add_nameserver"
423
424         #
425         # Loop forever until the user has finished configuring nameservers
426         #
427         while :; do
428                 #
429                 # Re/Build list of nameservers
430                 #
431                 local nameservers
432                 f_resolv_conf_nameservers nameservers
433                 menu_list=$(
434                         index=1
435
436                         echo "'X $msg_exit' '$opt_exit'" 
437                         index=$(( $index + 1 ))
438
439                         echo "'A $msg_add'  '$opt_add'" 
440                         index=$(( $index + 1 ))
441
442                         for ns in $nameservers; do
443                                 [ $index -lt ${#DIALOG_MENU_TAGS} ] || break
444                                 tag=$( f_substr "$DIALOG_MENU_TAGS" $index 1 )
445                                 echo "'$tag nameserver' '$ns'"
446                                 index=$(( $index + 1 ))
447                         done
448                 )
449
450                 #
451                 # Display configuration-edit menu
452                 #
453                 eval f_dialog_menu_size height width rows \
454                                         \"\$DIALOG_TITLE\"     \
455                                         \"\$DIALOG_BACKTITLE\" \
456                                         \"\$prompt\"           \
457                                         \"\$hline\"            \
458                                         $menu_list
459                 local tag
460                 tag=$( eval $DIALOG \
461                         --title \"\$DIALOG_TITLE\"         \
462                         --backtitle \"\$DIALOG_BACKTITLE\" \
463                         --hline \"\$hline\"                \
464                         --ok-label \"\$msg_ok\"            \
465                         --cancel-label \"\$msg_cancel\"    \
466                         --default-item \"\$defaultitem\"   \
467                         --menu \"\$prompt\"                \
468                         $height $width $rows               \
469                         $menu_list                         \
470                         2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD
471                 )
472                 local retval=$?
473                 f_dialog_data_sanitize tag
474
475                 # Return if "Cancel" was chosen (-1) or ESC was pressed (255)
476                 if [ $retval -ne $DIALOG_OK ]; then
477                         return $retval
478                 else
479                         # Only update default-item on success
480                         defaultitem="$tag"
481                 fi
482
483                 case "$tag" in
484                 "X $msg_exit") break ;;
485                 "A $msg_add")
486                         f_dialog_input_nameserver
487                         ;;
488                 *)
489                         local n ns
490                         n=$( eval f_dialog_menutag2index \"\$tag\" $menu_list )
491                         ns=$( eval f_dialog_menutag2item \"\$tag\" $menu_list )
492                         f_dialog_input_nameserver $(( $n - 2 )) "$ns"
493                         ;;
494                 esac
495         done
496 }
497
498 ############################################################ MAIN
499
500 f_dprintf "%s: Successfully loaded." networking/resolv.subr
501
502 fi # ! $_NETWORKING_RESOLV_SUBR