if [ ! "$_NETWORKING_RESOLV_SUBR" ]; then _NETWORKING_RESOLV_SUBR=1 # # Copyright (c) 2006-2013 Devin Teske # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # ############################################################ INCLUDES BSDCFG_SHARE="/usr/share/bsdconfig" . $BSDCFG_SHARE/common.subr || exit 1 f_dprintf "%s: loading includes..." networking/resolv.subr f_include $BSDCFG_SHARE/dialog.subr f_include $BSDCFG_SHARE/media/tcpip.subr f_include $BSDCFG_SHARE/networking/common.subr f_include $BSDCFG_SHARE/networking/ipaddr.subr f_include $BSDCFG_SHARE/strings.subr BSDCFG_LIBE="/usr/libexec/bsdconfig" APP_DIR="120.networking" f_include_lang $BSDCFG_LIBE/$APP_DIR/include/messages.subr ############################################################ CONFIGURATION # # When updating resolv.conf(5), should we populate the `search' directive with # all possible sub-domains? In example, if the domain is "sub.domain.com", when # the below option is set to 1, include both "sub.domain.com" and "domain.com" # in the `search' directive, otherwise use only "sub.domain.com". # # When enabled (set to 1), specify the minimum number of dots required for each # `search' domain by setting the second option below, `RESOLVER_SEARCH_NDOTS'. # : ${RESOLVER_SEARCH_DOMAINS_ALL:=1} : ${RESOLVER_SEARCH_NDOTS:=1} ############################################################ FUNCTIONS # f_resolv_conf_domain # # Returns the domain configured in resolv.conf(5). # f_resolv_conf_domain() { tail -r "$RESOLV_CONF" 2> /dev/null | awk \ ' BEGIN { found = 0 } ( tolower($1) == "domain" ) \ { print $2 found = 1 exit } END { exit ! found } ' } # f_resolv_conf_search # # Returns the search configured in resolv.conf(5). # f_resolv_conf_search() { tail -r "$RESOLV_CONF" 2> /dev/null | awk \ ' BEGIN { found = 0 } { tl0 = tolower($0) if ( match(tl0, /^[[:space:]]*search[[:space:]]+/) ) { search = substr($0, RLENGTH + 1) sub(/[[:space:]]*#.*$/, "", search) gsub(/[[:space:]]+/, " ", search) print search found = 1 exit } } END { exit ! found } ' } # f_dialog_resolv_conf_update $hostname # # Updates the search/domain directives in resolv.conf(5) given a valid fully- # qualified hostname. # # This function is a two-parter. Below is the awk(1) portion of the function, # afterward is the sh(1) function which utilizes the below awk script. # f_dialog_resolv_conf_update_awk=' # Variables that should be defined on the invocation line: # -v domain="domain" # -v search_all="0|1" # -v search_ndots="1+" # BEGIN { domain_found = search_found = 0 if ( search_all ) { search = "" subdomain = domain if ( search_ndots < 1 ) search_ndots = 1 ndots = split(subdomain, labels, ".") - 1 while ( ndots-- >= search_ndots ) { if ( length(search) ) search = search " " search = search subdomain sub(/[^.]*\./, "", subdomain) } } else search = domain } { if ( domain_found && search_found ) { print; next } tl0 = tolower($0) if ( ! domain_found && \ match(tl0, /^[[:space:]]*domain[[:space:]]+/) ) \ { if ( length(domain) ) { printf "%s%s\n", substr($0, 0, RLENGTH), domain domain_found = 1 } } else if ( ! search_found && \ match(tl0, /^[[:space:]]*search[[:space:]]+/) ) \ { if ( length(search) ) { printf "%s%s\n", substr($0, 0, RLENGTH), search search_found = 1 } } else print } END { if ( ! search_found && length(search) ) printf "search\t%s\n", search if ( ! domain_found && length(domain) ) printf "domain\t%s\n", domain } ' f_dialog_resolv_conf_update() { local funcname=f_dialog_resolv_conf_update local hostname="$1" # # Extrapolate the desired domain search parameter for resolv.conf(5) # local search nfields ndots domain="${hostname#*.}" if [ "$RESOLVER_SEARCH_DOMAINS_ALL" = "1" ]; then search= IFS=. f_count_ifs nfields "$domain" ndots=$(( $nfields - 1 )) while [ $ndots -ge ${RESOLVER_SEARCH_NDOTS:-1} ]; do search="$search $domain" domain="${domain#*.}" ndots=$(( $ndots - 1 )) done search="${search# }" domain="${hostname#*.}" else search="$domain" fi # # Save domain/search information only if different from resolv.conf(5) # if [ "$domain" != "$( f_resolv_conf_domain )" -o \ "$search" != "$( f_resolv_conf_search )" ] then f_dialog_info "Saving new domain/search settings" \ "to resolv.conf(5)..." # # Create a new temporary file to write our resolv.conf(5) # update with our new `domain' and `search' directives. # local tmpfile f_eval_catch -dk tmpfile $funcname mktemp \ 'mktemp -t "%s"' "$tmpfile" || return $DIALOG_CANCEL # # Fixup permissions and ownership (mktemp(1) creates the # temporary file with 0600 permissions -- change the # permissions and ownership to match resolv.conf(5) before # we write it out and mv(1) it into place). # local mode owner f_eval_catch -dk mode $funcname stat \ 'stat -f "%%#Lp" "%s"' "$RESOLV_CONF" || mode=0644 f_eval_catch -dk owner $funcname stat \ 'stat -f "%%u:%%g" "%s"' "$RESOLV_CONF" || owner="root:wheel" f_eval_catch -d $funcname chmod \ 'chmod "%s" "%s"' "$mode" "$tmpfile" f_eval_catch -d $funcname chown \ 'chown "%s" "%s"' "$owner" "$tmpfile" # # Operate on resolv.conf(5), replacing only the last # occurrences of `domain' and `search' directives (or add # them to the top if not found), in strict-adherence to the # following entry in resolver(5): # # The domain and search keywords are mutually exclusive. # If more than one instance of these keywords is present, # the last instance will override. # # NOTE: If RESOLVER_SEARCH_DOMAINS_ALL is set to `1' in the # environment, all sub-domains will be added to the `search' # directive, not just the FQDN. # local domain="${hostname#*.}" new_contents [ "$domain" = "$hostname" ] && domain= new_contents=$( tail -r "$RESOLV_CONF" 2> /dev/null ) new_contents=$( echo "$new_contents" | awk \ -v domain="$domain" \ -v search_all="${RESOLVER_SEARCH_DOMAINS_ALL:-1}" \ -v search_ndots="${RESOLVER_SEARCH_NDOTS:-1}" \ "$f_dialog_resolv_conf_update_awk" ) # # Write the temporary file contents and move the temporary # file into place. # echo "$new_contents" | tail -r > "$tmpfile" || return $DIALOG_CANCEL f_eval_catch -d $funcname mv \ 'mv "%s" "%s"' "$tmpfile" "$RESOLV_CONF" fi } # f_dialog_input_nameserver [ $n $nameserver ] # # Allows the user to edit a given nameserver. The first argument is the # resolv.conf(5) nameserver ``instance'' integer. For example, this will be one # if editing the first nameserver instance, two if editing the second, three if # the third, ad nauseum. If this argument is zero, null, or missing, the value # entered by the user (if non-null) will be added to resolv.conf(5) as a new # `nameserver' entry. The second argument is the IPv4 address of the nameserver # to be edited -- this will be displayed as the initial value during the edit. # # Taint-checking is performed when editing an existing entry (when the second # argument is one or higher) in that the first argument must match the current # value of the Nth `nameserver' instance in resolv.conf(5) else an error is # generated discarding any/all changes. # # This function is a two-parter. Below is the awk(1) portion of the function, # afterward is the sh(1) function which utilizes the below awk script. # f_dialog_input_nameserver_edit_awk=' # Variables that should be defined on the invocation line: # -v nsindex="1+" # -v old_value="..." # -v new_value="..." # BEGIN { if ( nsindex < 1 ) exit 1 found = n = 0 } { if ( found ) { print; next } if ( match(tolower($0), /^[[:space:]]*nameserver[[:space:]]+/)) { if ( ++n == nsindex ) { if ( $2 != old_value ) exit 2 if ( new_value != "" ) printf "%s%s\n", \ substr($0, 0, RLENGTH), new_value found = 1 } else print } else print } END { if ( ! found ) exit 3 } ' f_dialog_input_nameserver() { local funcname=f_dialog_input_nameserver local index="${1:-0}" old_ns="$2" new_ns local ns="$old_ns" # # Perform sanity checks # f_isinteger "$index" || return $DIALOG_CANCEL [ $index -ge 0 ] || return $DIALOG_CANCEL local msg if [ $index -gt 0 ]; then if [ "$USE_XDIALOG" ]; then msg="$xmsg_please_enter_nameserver_existing" else msg="$msg_please_enter_nameserver_existing" fi else msg="$msg_please_enter_nameserver" fi # # Loop until the user provides taint-free input. # while :; do f_dialog_input new_ns "$msg" "$ns" \ "$hline_num_punc_tab_enter" || return $? # Take only the first "word" of the user's input new_ns="${new_ns%%[$IFS]*}" # Taint-check the user's input [ "$new_ns" ] || break f_dialog_validate_ipaddr "$new_ns" && break # Update prompt to allow user to re-edit previous entry ns="$new_ns" done # # Save only if the user changed the nameserver. # if [ $index -eq "0" -a "$new_ns" ]; then f_dialog_info "$msg_saving_nameserver" printf "nameserver\t%s\n" "$new_ns" >> "$RESOLV_CONF" return $DIALOG_OK elif [ $index -gt 0 -a "$old_ns" != "$new_ns" ]; then if [ "$new_ns" ]; then msg="$msg_saving_nameserver_existing" else msg="$msg_removing_nameserver" fi f_dialog_info "$msg" # # Create a new temporary file to write our new resolv.conf(5) # local tmpfile f_eval_catch -dk tmpfile $funcname mktemp \ 'mktemp -t "%s"' "$pgm" || return $DIALOG_CANCEL # # Quietly fixup permissions and ownership # local mode owner f_eval_catch -dk mode $funcname stat \ 'stat -f "%%#Lp" "%s"' "$RESOLV_CONF" || mode=0644 f_eval_catch -dk owner $funcname stat \ 'stat -f "%%u:%%g" "%s"' "$RESOLV_CONF" || owner="root:wheel" f_eval_catch -d $funcname chmod \ 'chmod "%s" "%s"' "$mode" "$tmpfile" f_eval_catch -d $funcname chown \ 'chown "%s" "%s"' "$owner" "$tmpfile" # # Operate on resolv.conf(5) # local new_contents new_contents=$( awk -v nsindex="$index" \ -v old_value="$old_ns" \ -v new_value="$new_ns" \ "$f_dialog_input_nameserver_edit_awk" \ "$RESOLV_CONF" ) # # Produce an appropriate error message if necessary. # local retval=$? case $retval in 1) f_die 1 "$msg_internal_error_nsindex_value" "$nsindex" ;; 2) f_show_msg "$msg_resolv_conf_changed_while_editing" return $retval ;; 3) f_show_msg "$msg_resolv_conf_entry_no_longer_exists" return $retval ;; esac # # Write the temporary file contents and move the temporary # file into place. # echo "$new_contents" > "$tmpfile" || return $DIALOG_CANCEL f_eval_catch -d $funcname mv \ 'mv "%s" "%s"' "$tmpfile" "$RESOLV_CONF" fi } # f_dialog_menu_nameservers # # Edit the nameservers in resolv.conf(5). # f_dialog_menu_nameservers() { local prompt="$msg_dns_configuration" local menu_list # Calculated below local hline="$hline_arrows_tab_enter" local defaultitem= local height width rows local opt_exit="$msg_return_to_previous_menu" local opt_add="$msg_add_nameserver" # # Loop forever until the user has finished configuring nameservers # while :; do # # Re/Build list of nameservers # local nameservers f_resolv_conf_nameservers nameservers menu_list=$( index=1 echo "'X $msg_exit' '$opt_exit'" index=$(( $index + 1 )) echo "'A $msg_add' '$opt_add'" index=$(( $index + 1 )) for ns in $nameservers; do [ $index -lt ${#DIALOG_MENU_TAGS} ] || break f_substr -v tag "$DIALOG_MENU_TAGS" $index 1 echo "'$tag nameserver' '$ns'" index=$(( $index + 1 )) done ) # # Display configuration-edit menu # eval f_dialog_menu_size height width rows \ \"\$DIALOG_TITLE\" \ \"\$DIALOG_BACKTITLE\" \ \"\$prompt\" \ \"\$hline\" \ $menu_list local tag tag=$( eval $DIALOG \ --title \"\$DIALOG_TITLE\" \ --backtitle \"\$DIALOG_BACKTITLE\" \ --hline \"\$hline\" \ --ok-label \"\$msg_ok\" \ --cancel-label \"\$msg_cancel\" \ --default-item \"\$defaultitem\" \ --menu \"\$prompt\" \ $height $width $rows \ $menu_list \ 2>&1 >&$DIALOG_TERMINAL_PASSTHRU_FD ) local retval=$? f_dialog_data_sanitize tag # Return if "Cancel" was chosen (-1) or ESC was pressed (255) if [ $retval -ne $DIALOG_OK ]; then return $retval else # Only update default-item on success defaultitem="$tag" fi case "$tag" in "X $msg_exit") break ;; "A $msg_add") f_dialog_input_nameserver ;; *) local n ns n=$( eval f_dialog_menutag2index \"\$tag\" $menu_list ) ns=$( eval f_dialog_menutag2item \"\$tag\" $menu_list ) f_dialog_input_nameserver $(( $n - 2 )) "$ns" ;; esac done } ############################################################ MAIN f_dprintf "%s: Successfully loaded." networking/resolv.subr fi # ! $_NETWORKING_RESOLV_SUBR