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