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