diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 18ff48d6e..c0177b58d 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -12,5 +12,5 @@ jobs: - uses: actions/checkout@v6 - uses: codespell-project/actions-codespell@master with: - skip: ca_hashes.txt,tls_data.txt,*.pem,OPENSSL-LICENSE.txt,CREDITS.md,openssl.cnf,fedora-dirk-ipv6.diff,testssl.1 - ignore_words_list: borken,gost,ciph,ba,bloc,isnt,chello,fo,alle,anull + skip: ca_hashes.txt,tls_data.txt,*.pem,OPENSSL-LICENSE.txt,CREDITS.md,openssl.cnf,testssl.1 + ignore_words_list: borken,gost,ciph,ba,bloc,isnt,chello,fo,alle,anull,expt diff --git a/.github/workflows/unit_tests_macos.yml b/.github/workflows/unit_tests_macos.yml index 0e5f49105..ace1cc96a 100644 --- a/.github/workflows/unit_tests_macos.yml +++ b/.github/workflows/unit_tests_macos.yml @@ -45,6 +45,8 @@ jobs: printf "%s\n" "----------" bash --version printf "%s\n" "----------" + echo $PATH + printf "%s\n" "----------" - name: Install perl modules run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e725f7d3..d98a7b584 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * Bump SSLlabs rating guide to 2009r * Check for Opossum vulnerability * Enable IPv6 automagically, i.e. if target via IPv6 is reachable just (also) scan it +* Detect and show DNS HTTPS RR (RFC 9460) * Provide an FAQ ### Features implemented / improvements in 3.2 diff --git a/t/baseline_data/default_testssl.csvfile b/t/baseline_data/default_testssl.csvfile index f43bbb912..155ccf798 100644 --- a/t/baseline_data/default_testssl.csvfile +++ b/t/baseline_data/default_testssl.csvfile @@ -1,5 +1,6 @@ "id","fqdn/ip","port","severity","finding","cve","cwe" "engine_problem","/","443","WARN","No engine or GOST support via engine with your ./bin/openssl.Linux.x86_64","","" +"DNS_HTTPS_rrecord","testssl.sh/81.169.235.32","443","OK","1 . alpn='h2'","","" "service","testssl.sh/81.169.235.32","443","INFO","HTTP","","" "pre_128cipher","testssl.sh/81.169.235.32","443","INFO","No 128 cipher limit bug","","" "SSLv2","testssl.sh/81.169.235.32","443","OK","not offered","","" diff --git a/testssl.sh b/testssl.sh index fbf4a8f92..125ceeca7 100755 --- a/testssl.sh +++ b/testssl.sh @@ -387,6 +387,11 @@ HAS_IDN2=false HAS_AVAHIRESOLVE=false HAS_DSCACHEUTIL=false HAS_DIG_NOIDNOUT=false +HAS_DIG_HTTPS=false # *_HTTPS: whether the binaries support HTTPS RR directly +HAS_DRILL_HTTPS=false +HAS_HOST_HTTPS=false +HAS_NSLOOKUP_HTTPS=false + HAS_XXD=false OSSL_CIPHERS_S="" @@ -2354,7 +2359,7 @@ hex2binary() { # convert 414243 into ABC hex2ascii() { - hex2binary $1 + hex2binary "$1" } # arg1: text string @@ -2501,6 +2506,7 @@ s_client_options() { # determines whether the port has an HTTP service running or not (plain TLS, no STARTTLS) # arg1 could be the protocol determined as "working". IIS6 needs that. +# sets global $SERVICE # service_detection() { local -i was_killed @@ -2545,24 +2551,29 @@ service_detection() { debugme head -50 $TMPFILE | sed -e '//,$d' -e '//,$d' -e '/ trying HTTP checks" SERVICE=HTTP fileout "${jsonID}" "DEBUG" "Couldn't determine service -- ASSUME_HTTP set" elif [[ "$CLIENT_AUTH" == required ]] && [[ -z $MTLS ]]; then - out " certificate-based authentication without providing client certificate and private key => skipping all HTTP checks" - echo "certificate-based authentication without providing client certificate and private key => skipping all HTTP checks" >$TMPFILE + out " certificate-based authentication without providing client certificate and private key => skipping all HTTP checks" | tee $TMPFILE fileout "${jsonID}" "INFO" "certificate-based authentication without providing client certificate and private key => skipping all HTTP checks" else out " Couldn't determine what's running on port $PORT" @@ -9395,6 +9406,7 @@ certificate_info() { local first=true local badocsp=1 local len_cert_serial=0 + local avoid_complaints="^(1\.1\.1\.1|1\.0\.0\.1|8\.8\.8\.8|8\.8\.4\.4|9\.9\.9\.9)$" if [[ $number_of_certificates -gt 1 ]]; then [[ $certificate_number -eq 1 ]] && outln @@ -10272,9 +10284,9 @@ certificate_info() { out "$indent"; pr_bold " DNS CAA RR"; out " (experimental) " jsonID="DNS_CAArecord" - if is_ipv4addr "$NODE" || is_ipv6addr "$NODE"; then - out "not checked (IP address scan -- no domain to query)" - fileout "${jsonID}${json_postfix}" "INFO" "not checked (IP address scan)" + if [[ ! $tmp =~ [a-zA-Z] ]] && [[ ! $tmp =~ $avoid_complaints ]]; then + out "not checked: IP address scan, no domain to query" + fileout "${jsonID}${json_postfix}" "INFO" "not checked IP address scan, no domain to query" else caa_node="$NODE" caa="" @@ -17731,7 +17743,7 @@ run_ticketbleed() { local hint="" [[ -n "$STARTTLS" ]] && return 0 - pr_bold " Ticketbleed"; out " ($cve), experiment. " + pr_bold " Ticketbleed"; out " ($cve), experimental " if [[ "$SERVICE" != HTTP ]] && [[ "$CLIENT_AUTH" != required ]]; then outln "(applicable only for HTTP service)" @@ -22352,6 +22364,8 @@ get_local_a() { # check_resolver_bins() { local saved_openssl_conf="$OPENSSL_CONF" + local testhost=localhost + local str="" OPENSSL_CONF="" # see https://github.com/testssl/testssl.sh/issues/134 type -p dig &> /dev/null && HAS_DIG=true @@ -22376,12 +22390,38 @@ check_resolver_bins() { HAS_DIG_NOIDNOUT=true fi fi + + # Pre-checking the following for HTTPS RR, see get_https_rrecord() + if "$HAS_DIG"; then + str=$(dig $DIG_R +short $testhost HTTPS) + if [[ -z "$str" ]] && [[ ! "$str" =~ 127.0.0.1 ]] && \ + # MacOS runners are problematic otherwise: + dig $DIG_R +nocomments $testhost HTTPS | grep -q 'IN.*HTTPS'; then + HAS_DIG_HTTPS=true + fi + elif "$HAS_DRILL"; then + if drill $testhost HTTPS | grep -Eq 'IN.*HTTPS'; then + HAS_DRILL_HTTPS=true + fi + elif "$HAS_HOST"; then + host -t HTTPS $testhost 2>&1 | grep -q 'invalid type' + if [[ $? -ne 0 ]]; then + HAS_HOST_HTTPS=true + fi + elif "$HAS_NSLOOKUP"; then + nslookup -type=HTTPS $testhost | grep -q 'unknown query type' + if [[ $? -ne 0 ]]; then + HAS_NSLOOKUP_HTTPS=true + fi + fi + OPENSSL_CONF="$saved_openssl_conf" # see https://github.com/testssl/testssl.sh/issues/134 return 0 } # arg1: a host name. Returned will be 0-n IPv4 addresses # watch out: $1 can also be a cname! --> all checked +# get_a_record() { local ip4="" local saved_openssl_conf="$OPENSSL_CONF" @@ -22435,6 +22475,7 @@ get_a_record() { # arg1: a host name. Returned will be 0-n IPv6 addresses # watch out: $1 can also be a cname! --> all checked +# get_aaaa_record() { local ip6="" local saved_openssl_conf="$OPENSSL_CONF" @@ -22484,9 +22525,12 @@ get_aaaa_record() { echo "$ip6" } + # RFC6844: DNS Certification Authority Authorization (CAA) Resource Record # arg1: domain to check for -get_caa_rr_record() { +#FIXME: should be refactored, see get_https_rrecord() +# +get_caa_rrecord() { local raw_caa="" local hash len line local -i len_caa_property @@ -22514,12 +22558,16 @@ get_caa_rr_record() { raw_caa="$(drill $1 type257 | awk '/'"^${1}"'.*CAA/ { print $5,$6,$7 }')" elif "$HAS_HOST"; then raw_caa="$(host -t type257 $1)" - if grep -Ewvq "has no CAA|has no TYPE257" <<< "$raw_caa"; then - raw_caa="$(sed -e 's/^.*has CAA record //' -e 's/^.*has TYPE257 record //' <<< "$raw_caa")" + if [[ "$raw_caa" =~ "has no CAA|has no TYPE257" ]]; then + raw_caa="" + else + raw_caa="${raw_caa/$1 has CAA record /}" + raw_caa="${raw_caa/$1 has TYPE257 record /}" fi elif "$HAS_NSLOOKUP"; then raw_caa="$(strip_lf "$(nslookup -type=type257 $1 | grep -w rdata_257)")" if [[ -n "$raw_caa" ]]; then + #FIXME: modernize here or see HTTPS RR raw_caa="$(sed 's/^.*rdata_257 = //' <<< "$raw_caa")" fi else @@ -22562,11 +22610,303 @@ get_caa_rr_record() { return 1 fi -# to do: +#TODO: # 4: check whether $1 is a CNAME and take this return 0 } + +# Service Binding and Parameter Specification via the DNS (SVCB and HTTPS Resource Records). +# https://www.rfc-editor.org/rfc/rfc9460.html +# arg1: domain to check for +# returns: string for record +# +get_https_rrecord() { + local raw_https="" + local hash="" line="" + local len # better fix as integer? + local len_alpnID="" + local alpnID="" + local alpnID_wire="" + local saved_openssl_conf="$OPENSSL_CONF" + local all_https="" + local noidnout="" + local svc_priority="" + + [[ -n "$NODNS" ]] && return 2 # if minimum DNS lookup was instructed, leave here + "$HAS_DIG_NOIDNOUT" && noidnout="+noidnout" + + # There's the possibility to query HTTPS RR records directly like "dig +short HTTPS dev.testssl.sh", + # "drill HTTPS FQDN" or "nslookup -type=HTTPS FQDN". This works for new binaries only. Thus we try first + # whether we can query the HTTPS records directly as this gives us that already everything we want in + # in clear text and also we can avoid to parse the encoded formats. + + # "tail -1" and the awk commands make sure we use the right lines when we encounter a CNAME + OPENSSL_CONF="" + if "$HAS_DIG_HTTPS"; then + text_httpsrr="$(dig $DIG_R +short +search +timeout=3 +tries=3 $noidnout HTTPS "$1" 2>/dev/null | tail -1)" + elif "$HAS_DRILL_HTTPS"; then + text_httpsrr="$(drill -Q HTTPS "$1" 2>/dev/null | tail -1)" + elif "$HAS_HOST_HTTPS"; then + text_httpsrr="$(host -t HTTPS "$1" 2>/dev/null | awk -F'HTTP service bindings ' '/HTTP service bindings /{print $2}')" + elif "$HAS_NSLOOKUP_HTTPS"; then + text_httpsrr="$(nslookup -type=HTTPS "$1" | awk -F'rdata_65 = ' '/rdata_65 =/{print $2}' )" + fi + + if [[ -n "$text_httpsrr" ]]; then + safe_echo "$text_httpsrr" + OPENSSL_CONF="$saved_openssl_conf" # see https://github.com/drwetter/testssl.sh/issues/134 + return 0 + elif "$HAS_DIG_HTTPS" || "$HAS_DRILL_HTTPS" || "$HAS_HOST_HTTPS" || "$HAS_NSLOOKUP_HTTPS"; then + # no record despite binaries are "HTTPS record aware" + OPENSSL_CONF="$saved_openssl_conf" + return 0 + fi + + # As we didn't succeed yet, we need to try parsing the raw output. First is to get the TYPE65 record + # as text. These days (2026) it's not that common anymore. Mac is the party pooper as it normally returns + # a hex stream only --in 2026. Here's how output of old+ancient client DNS binaries may look like with TYPE65 + + # for host: + # 1) 'google.com has HTTPS record 1 . alpn="h2,h3" ' + # 2) 'google.com has TYPE65 record \# 13 0001000001000602683202683 ' + + # for drill and dig it's like + #1) google.com. 18665 IN TYPE65 \# 13 00010000010006026832026833 + #2) google.com. 18301 IN HTTPS 1 . alpn="h2,h3" + + # nslookup: + # 1) dev.testssl.sh rdata_65 = 1 . alpn="h2" + # 2) dev.testssl.sh rdata_65 = \# 10 00010000010003026832 + + if "$HAS_DIG"; then + raw_https="$(dig $DIG_R +short +search +timeout=3 +tries=3 $noidnout type65 "$1" 2>/dev/null)" + # empty if there's no such record + elif "$HAS_DRILL"; then + raw_https="$(drill "$1" type65 | grep -v '^;;' | awk '/'"^${1}"'.*TYPE65/ { print substr($0,index($0,$5)) }' )" # from 5th field onwards + # empty if there's no such record + elif "$HAS_HOST"; then + raw_https="$(host -t type65 "$1")" + if [[ "$raw_https" =~ "has no HTTPS|has no TYPE65" ]]; then + raw_https="" + else + raw_https="${raw_https/$1 has HTTPS record /}" + raw_https="${raw_https/$1 has TYPE65 record /}" + fi + elif "$HAS_NSLOOKUP"; then + raw_https="$(strip_lf "$(nslookup -type=type65 "$1" | awk '/'"^${1}"'.*rdata_65/ { print substr($0,index($0,$4)) }' )")" + # empty if there's no such record + else + return 1 + # No dig, drill, host, or nslookup --> complaint was elsewhere already + fi + OPENSSL_CONF="$saved_openssl_conf" # We're done now with openssl, see https://github.com/drwetter/testssl.sh/issues/134 + + # Now comes the third, more tricky part and the last straw --> parsing the hex stream which was returned if it was returned. + # Format is like: https://www.rfc-editor.org/rfc/rfc3597 (plus updates) + + # dev.testssl.sh: + # 1 . alpn="h2" port=443 ipv6hint=2a01:238:4308:a920:1000:0:b:1337 + # + # 36 000100000100030268320003000201BB000600102A0102384308A920 10000000000B1337 + # TL alpn| L h 2 | for esni (https://datatracker.ietf.org/doc/draft-ietf-tls-esni/) + # Nice description: https://www.netmeister.org/blog/https-rrs.html + # + # 1. alpn="h3,h2" ipv4hint=104.21.34.154,172.67.205.231 + # 136 00010000010006026833026832000400086815229AAC43CDE7000500 470045FE0D0041F3002000202BD0935ED66980C1862F2570C0D6014D + # TL alpn| L h 3 L h 2 |IPv4#1||IPv4#2| + # + # ech=AEX+DQBBzgAgACBQGA9EFbz+PkJAXSXtcqJluxLlhxIgzhJ+GhTtRd4nJQAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAA= ipv6hint=2606:4700:3031::ac43:cde7,2606:4700:3036::6815:229a + # 733A7CFAAEA5E4DD9CA43D4C24199E330004000100010012636C6F75 64666C6172652D6563682E636F6D0000000600202606470030310000 00000000AC43CDE72606470030360000000000006815229A + # | cloudflare-ech.com | IPv6#1 #IPv6#2 + + # Be aware that all variables are strings here! Therefore we use double quotes so that e.g. 03 won't become 3 + # For now the following only works for short entries like 1. alpn=h2,h3 + +local text="" + + if [[ -z "$raw_https" ]]; then + return 1 + elif [[ "$raw_https" =~ \#\ [0-9][0-9] ]]; then + # signals that we're on the right track with type65 interpretation + read hash len line <<< "$raw_https" + # \# 10 00010000010003026832 + # \# 36 000100000100030268320003000201BB000600102A01023842816755 10000000000B1337 + + len=${len// /} # remove spaces + if [[ "${line:0:4}" == 0001 ]]; then # marker to proceed, belongs to SvcPriority, see rfc9460, 2.4.3 + svc_priority=$(printf "%0d" "$((10#${line:2:2}))") # 1 is most often, 0 is alias + if [[ $svc_priority == "1" ]]; then + # mock text representation + svc_priority+=" . " #FIXME? needs more testing + text="${text}${svc_priority}" + fi + len_entry=${line:12:2} + len_entry=$(( ((10#$len_entry)) * 2 )) # make sure we count in the right system + entry=${line:14:$len_entry} + + # Service Parameter Keys https://www.rfc-editor.org/info/rfc9460/#name-initial-contents + case ${line:8:2} in + 00) # = "mandatory", skipping that + ;; + 01) # = "alpn" + text+=$(decode_https_rr_alpn $entry) ;; + 02) # = "no-default-alpn", skipping that + ;; + 03) # = "port" + text+=$(decode_https_rr_port $entry) ;; + 04) # = "ipv4hint" + text+=$(decode_https_rr_ipv4hint $entry) ;; + 05) # = "ech" + text+=$(decode_https_rr_ech $entry) ;; + 06) # = "ipv6hint" + text+=$(decode_https_rr_ipv6hint $entry) ;; + 07) # = "dohpath" + text+=$(decode_https_rr_dohpath $entry) ;; + esac + else + out "please report unknown HTTPS RR $line with flag @ $NODE" + return 7 + fi + safe_echo "$text" + [[ $DEBUG -eq 1 ]] && echo "$hash $len $line" >&2 + [[ $DEBUG -eq 1 ]] && echo "key: ${line:localPTR:4}" >&2 + fi + return 0 +} + +# key 1 — alpn: one or more length-prefixed protocol strings +# +decode_https_rr_alpn() { + local entry="$1" + local -i len="${#entry}" + local -i ptr=0 + local alpn_wire="" str="" + local alpn_len="" + + while (( ptr < len )); do + [[ -n "$alpn_str" ]] && alpn_str+="," # add a comma in the >=2 round + alpn_len=${entry:$ptr:2} + alpn_len=$(( ((10#$alpn_len)) * 2 )) # conversion, make sure it's the right format + + ptr=$((ptr + 2)) # len field is always 2 bytes + alpn_wire=${entry:$ptr:$alpn_len} + str=$(hex2ascii $alpn_wire) + ptr=$((ptr + alpn_len)) + alpn_str+="$str" + done + safe_echo "alpn=\"$alpn_str\"" +} + +# key 3 — port: single u16 override port +# +decode_https_rr_port() { + local entry="$1" + local -i len="${#entry}" + local -i ptr=2 + local port_wire="" str="" + + # we assume it's one port only and it starts at $ptr and is $len-$ptr long + port_wire=${entry:$ptr:$((len - ptr))} + str=$((16#$port_wire)) # hex2dec + port_str+="$str" + safe_echo "port=\"$port_str\"" +} + +# key 4 — ipv4hint: one or more 4-byte IPv4 addresse +# +decode_https_rr_ipv4() { + local entry="$1" + local -i len="${#entry}" + local -i ptr=2 + local ipv4_wire="" str="" + # we currently don't need that: + # local nr_ips="${1:0:2}" + + while (( ptr < len )); do + ipv4_wire=${entry:$ptr:2} + str=$((16#$ipv4_wire)) # hex2dec + ipv4_str+="$str" + + # if the end is not reached yet + # after address 2,4,6, 10,12,14, ... we need a dot + # after address 18, 16, ... we need a comma + + if [[ $len -ne $((ptr + 2)) ]]; then + if [[ $((ptr % 8 )) -eq 0 ]] ; then + ipv4_str+="," + else + ipv4_str+="." + fi + fi + ptr=$((ptr + 2)) # two bytes per octet + done + safe_echo "ipv4hint=\"$ipv4_str\"" +} + + +# key 5 — ech: opaque ECHConfigList blob, show as truncated hex +# +decode_https_rr_ech() { + echo +} + + +# key 6 — ipv6hint: one or more 16-byte IPv6 addresses +#FIXME: doesn't do IPv6 compression yet +decode_https_rr_ipv6() { + local entry="$1" + local -i len="${#entry}" + local -i ptr=2 # we start at pos 2 + local ipv6_wire="" str="" + # local nr_ips="${1:0:2}" + + while (( ptr < len )); do + ipv6_wire=${entry:$ptr:4} + ipv6_str+="$ipv6_wire" + + # We have 8 octets filled with zero if needed --> 32 chars + + if [[ $len -ne $((ptr + 4)) ]]; then + if [[ $((ptr % 30 )) -eq 0 ]] ; then # we have two bytes pointer 30+2=32 + ipv6_str+="," + else + ipv6_str+=":" + fi + fi + ptr=$((ptr + 4)) # two byte per octett + done + + ipv6_str="$(tolower "$ipv6_str")" + + safe_echo "ipv6hint=\"$ipv6_str\"" +} + + +# key 7 — dohpath: UTF-8 URI template for DNS-over-HTTPS +#FIXME --> to test! +# +decode_dohpath() { + local entry="$1" + local -i len="${#entry}" + # local len=$1 + local path="" + local -i i + + for (( i = 0; i < len; i++ )); do + path+=$(printf "\\$(printf '%03o' "${PARAM_VALUE_BYTES[$i]}")") + done + safe_echo "$path" +} + + + + # arg1: domain to check for. Returned will be the MX record as a string get_mx_record() { local mx="" @@ -23391,6 +23731,35 @@ determine_optimal_proto() { } +dns_https_rr () { + local jsonID="DNS_HTTPS_rrecord" + local https_rr="" + local indent="" + local https_rr_node="$NODE" + + out "$indent"; pr_bold " DNS HTTPS RR"; out " (expt.): " + if [[ -n "$NODNS" ]]; then + out "(instructed to minimize/skip DNS queries)" + fileout "${jsonID}" "INFO" "check skipped as instructed" + elif "$DNS_VIA_PROXY"; then + out "(instructed to use the proxy for DNS only)" + fileout "${jsonID}" "INFO" "check skipped as instructed (proxy)" + else + # append a dot if there was none + [[ $https_rr_node =~ '.'$ ]] || https_rr_node+="." + https_rr="$(get_https_rrecord $https_rr_node)" + if [[ -n "$https_rr" ]]; then + pr_svrty_good "yes" ; out ": " + prln_italic "$(out_row_aligned_max_width "$https_rr" "$indent " $TERM_WIDTH)" + fileout "${jsonID}" "OK" "$https_rr" + else + outln "--" + fileout "${jsonID}" "INFO" " no resource record found" + fi + fi +} + + # Check messages which needed to be processed. I.e. those which would have destroyed the nice # screen output and thus havve been postponed. This is just an idea and is only used once # but can be extended in the future. An array might be more handy @@ -23441,7 +23810,7 @@ determine_service() { fi GET_REQ11="GET $URL_PATH HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $ua\r\n${basicauth_header}${reqheader}Accept-Encoding: identity\r\nAccept: */*\r\nConnection: Close\r\n\r\n" determine_optimal_proto - # returns always 0: + # returns always 0 and sets $SERVICE service_detection $OPTIMAL_PROTO check_msg else # STARTTLS @@ -23514,7 +23883,7 @@ determine_service() { determine_optimal_sockets_params determine_optimal_proto "$1" - out " Service set:$CORRECT_SPACES STARTTLS via " + pr_bold " Service set"; out ":$CORRECT_SPACES STARTTLS via " out "$(toupper "$protocol")" [[ "$protocol" == mysql ]] && out " (experimental)" fileout "service" "INFO" "$protocol" @@ -23528,7 +23897,6 @@ determine_service() { # It comes handy later also for STARTTLS injection to define this global. When we do banner grabbing # or replace service_detection() we might not need that anymore SERVICE=$protocol - fi tmpfile_handle ${FUNCNAME[0]}.txt @@ -23594,7 +23962,7 @@ display_rdns_etc() { outln "$PROXYIP:$PROXYPORT " fi if [[ $(count_words "$IPADDRs2SHOW") -gt 1 ]]; then - out " Further IP addresses: $CORRECT_SPACES" + pr_bold " Further IP addresses"; out ": $CORRECT_SPACES" for ip in $IPADDRs2SHOW; do if [[ "$ip" == $NODEIP ]] || [[ "[$ip]" == $NODEIP ]]; then continue @@ -23615,11 +23983,11 @@ display_rdns_etc() { outln " A record via: $CORRECT_SPACES supplied IP \"$CMDLINE_IP\"" fi fi + pr_bold " rDNS " + out "$(printf "%-19s" "($nodeip):")" if [[ "$rDNS" =~ instructed ]]; then - out "$(printf " %-23s " "rDNS ($nodeip):")" out "$rDNS" elif [[ -n "$rDNS" ]]; then - out "$(printf " %-23s " "rDNS ($nodeip):")" out "$(out_row_aligned_max_width "$rDNS" " $CORRECT_SPACES" $TERM_WIDTH)" fi }