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