From 227a31b7883199c640decd64bd4692c4c48d57d4 Mon Sep 17 00:00:00 2001 From: David Cooper Date: Tue, 28 Mar 2017 13:54:54 -0400 Subject: [PATCH 01/27] out_row_aligned_max_width() This PR improves `out_row_aligned_max_width()` in a few ways: * It makes better use of bash's string manipulation capabilities in order to simplify the function. * It improves the function's performance. One of the most costly parts of `out_row_aligned_max_width()` was the while loop to print each entry in the text. Since there is only one place in the code where the the entries are not all printed the same ways (the list of supported curves printed by `run_pfs()`), the PR changes `out_row_aligned_max_width()` to just return a plain text string, which the calling function prints in the appropriate way. For the curves printed by `run_pfs()`, a new function, `out_row_aligned_max_width_by_entry()` takes care of getting the output from `out_row_aligned_max_width()` and then printing each entry appropriately. * The PR also introduces a trick so that when the TLS extensions are printed, the text for an extension won't get split across two rows. It does this by replacing the space charters within the text for an extension with "}", formatting the result with `out_row_aligned_max_width()`, and then converting the "}" back to space characters. --- testssl.sh | 118 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 68 insertions(+), 50 deletions(-) diff --git a/testssl.sh b/testssl.sh index 885e5e1..3272d4c 100755 --- a/testssl.sh +++ b/testssl.sh @@ -1222,63 +1222,73 @@ out_row_aligned() { } # prints text over multiple lines, trying to make no line longer than $max_width. -# Each line is indented with $spaces and each word in $text is printed using $print_function. +# Each line is indented with $spaces. out_row_aligned_max_width() { local text="$1" local spaces="$2" local -i max_width="$3" - local print_function="$4" local -i i len local cr=$'\n' - local line entry first=true last=false + local line first=true - max_width=$max_width-1 # at the moment we align to terminal width. This makes sure we don't wrap too late max_width=$max_width-${#spaces} len=${#text} while true; do - i=$max_width - if [[ $i -ge $len ]]; then + if [[ $len -lt $max_width ]]; then + # If the remaining text to print is shorter than $max_width, + # then just print it. i=$len else - while true; do - [[ "${text:i:1}" == " " ]] && break - [[ $i -eq 0 ]] && break - i=$i-1 - done - if [[ $i -eq 0 ]]; then - i=$max_width+1 - while true; do - [[ "${text:i:1}" == " " ]] && break - [[ $i -eq $len ]] && break - i+=1 - done + # Find the final space character in the text that is less than + # $max_width characters into the remaining text, and make the + # text up to that space character the next line to print. + line="${text:0:max_width}" + line="${line% *}" + i=${#line} + if [[ $i -eq $max_width ]]; then + # If there are no space characters in the first $max_width + # characters of the remaining text, then make the text up + # to the first space the next line to print. If there are + # no space characters in the remaining text, make the + # remaining text the next line to print. + line="${text#* }" + i=$len-${#line} + [[ $i -eq 0 ]] && i=$len fi fi - if [[ $i -eq $len ]]; then - line="$text" - if ! "$first"; then - out "${cr}${spaces}" - fi - last=true - else - line="${text:0:i}" - if ! "$first"; then - out "${cr}${spaces}" - fi - len=$len-$i-1 - i=$i+1 - text="${text:i:len}" - first=false - [[ $len -eq 0 ]] && last=true + if ! "$first"; then + tm_out "${cr}${spaces}" fi - while read entry; do - $print_function "$entry" ; out " " - done <<< "$(tr ' ' '\n' <<< "$line")" - "$last" && break + tm_out "${text:0:i}" + [[ $i -eq $len ]] && break + len=$len-$i-1 + i=$i+1 + text="${text:i:len}" + first=false + [[ $len -eq 0 ]] && break done return 0 } +out_row_aligned_max_width_by_entry() { + local text="$1" + local spaces="$2" + local -i max_width="$3" + local print_function="$4" + local resp entry prev_entry=" " + + resp="$(out_row_aligned_max_width "$text" "$spaces" "$max_width")" + while read -d " " entry; do + if [[ -n "$entry" ]]; then + $print_function "$entry" + elif [[ -n "$prev_entry" ]]; then + outln; out " " + fi + out " " + prev_entry="$entry" + done <<< "$resp" +} + tmpfile_handle() { mv $TMPFILE "$TEMPDIR/$NODEIP.$1" 2>/dev/null @@ -4877,7 +4887,7 @@ cipher_pref_check() { if [[ -n "$order" ]]; then outln out "$(printf " %-10s " "$proto: ")" - out_row_aligned_max_width "$order" " " $TERM_WIDTH out + out "$(out_row_aligned_max_width "$order" " " $TERM_WIDTH)" fileout "order_$p" "INFO" "Default cipher order for protocol $p: $order" fi done @@ -5773,7 +5783,7 @@ certificate_info() { while read san; do [[ -n "$san" ]] && all_san+="$san " done <<< "$sans" - out_row_aligned_max_width "$all_san" "$indent " $TERM_WIDTH pr_italic + pr_italic "$(out_row_aligned_max_width "$all_san" "$indent " $TERM_WIDTH)" fileout "${json_prefix}san" "INFO" "subjectAltName (SAN) : $all_san" else out "-- " @@ -6078,7 +6088,7 @@ run_server_defaults() { local -a previous_hostcert previous_intermediates keysize cipher local -a ocsp_response ocsp_response_status sni_used local -a ciphers_to_test success - local cn_nosni cn_sni sans_nosni sans_sni san + local cn_nosni cn_sni sans_nosni sans_sni san tls_extensions local alpn_proto alpn="" alpn_list_len_hex alpn_extn_len_hex success local -i alpn_list_len alpn_extn_len @@ -6216,7 +6226,16 @@ run_server_defaults() { fileout "tls_extensions" "INFO" "TLS server extensions (std): (none)" else #FIXME: we rather want to have the chance to print each ext in italcs or another format. Atm is a string of quoted strings -- that needs to be fixed at the root - out_row_aligned_max_width "$TLS_EXTENSIONS" " " $TERM_WIDTH out; outln + # out_row_aligned_max_width() places line breaks at space characters. + # So, in order to prevent the text for an extension from being broken + # across lines, temporarily replace space characters within the text + # of an extension with "}", and then convert the "}" back to space in + # the output of out_row_aligned_max_width(). + tls_extensions="${TLS_EXTENSIONS// /{}" + tls_extensions="${tls_extensions//\"{\"/\" \"}" + tls_extensions="$(out_row_aligned_max_width "$tls_extensions" " " $TERM_WIDTH)" + tls_extensions="${tls_extensions//{/ }" + outln "$tls_extensions" fileout "tls_extensions" "INFO" "TLS server extensions (std): $TLS_EXTENSIONS" fi @@ -6443,7 +6462,7 @@ run_pfs() { outln "${sigalg[i]}" fi done - ! "$WIDE" && out_row_aligned_max_width "$pfs_ciphers" " " $TERM_WIDTH out + ! "$WIDE" && out "$(out_row_aligned_max_width "$pfs_ciphers" " " $TERM_WIDTH)" debugme echo $pfs_offered "$WIDE" || outln fileout "pfs_ciphers" "INFO" "(Perfect) Forward Secrecy Ciphers: $pfs_ciphers" @@ -6524,7 +6543,7 @@ run_pfs() { if [[ -n "$curves_offered" ]]; then "$WIDE" && outln pr_bold " Elliptic curves offered: " - out_row_aligned_max_width "$curves_offered" " " $TERM_WIDTH pr_ecdh_curve_quality + out_row_aligned_max_width_by_entry "$curves_offered" " " $TERM_WIDTH pr_ecdh_curve_quality outln fileout "ecdhe_curves" "INFO" "Elliptic curves offered $curves_offered" fi @@ -10079,8 +10098,8 @@ run_beast(){ ! "$first" && out "$spaces" out "$(toupper $proto): " [[ -n "$higher_proto_supported" ]] && \ - out_row_aligned_max_width "$detected_cbc_ciphers" " " $TERM_WIDTH pr_svrty_low || \ - out_row_aligned_max_width "$detected_cbc_ciphers" " " $TERM_WIDTH pr_svrty_medium + pr_svrty_low "$(out_row_aligned_max_width "$detected_cbc_ciphers" " " $TERM_WIDTH)" || \ + pr_svrty_medium "$(out_row_aligned_max_width "$detected_cbc_ciphers" " " $TERM_WIDTH)" outln detected_cbc_ciphers="" # empty for next round first=false @@ -10417,7 +10436,7 @@ run_rc4() { fi fi done - ! "$WIDE" && out_row_aligned_max_width "$rc4_detected" " " $TERM_WIDTH pr_svrty_high + ! "$WIDE" && pr_svrty_high "$(out_row_aligned_max_width "$rc4_detected" " " $TERM_WIDTH)" outln "$WIDE" && pr_svrty_high "VULNERABLE (NOT ok)" fileout "rc4" "HIGH" "RC4: VULNERABLE, Detected ciphers: $rc4_detected" "$cve" "$cwe" "$hint" @@ -11665,8 +11684,7 @@ display_rdns_etc() { further_ip_addrs+="$ip " fi done - out_row_aligned_max_width "$further_ip_addrs" " $CORRECT_SPACES" $TERM_WIDTH out - outln + outln "$(out_row_aligned_max_width "$further_ip_addrs" " $CORRECT_SPACES" $TERM_WIDTH)" fi if "$LOCAL_A"; then outln " A record via $CORRECT_SPACES /etc/hosts " @@ -11675,7 +11693,7 @@ display_rdns_etc() { fi if [[ -n "$rDNS" ]]; then out "$(printf " %-23s %s" "rDNS ($nodeip):")" - out_row_aligned_max_width "$rDNS" " $CORRECT_SPACES" $TERM_WIDTH out + out "$(out_row_aligned_max_width "$rDNS" " $CORRECT_SPACES" $TERM_WIDTH)" fi } From 05ea5675b89964b20c11284661535f015b62b0ed Mon Sep 17 00:00:00 2001 From: Dirk Date: Wed, 29 Mar 2017 10:44:22 +0200 Subject: [PATCH 02/27] one line per variable type --- testssl.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testssl.sh b/testssl.sh index 3272d4c..a1c854c 100755 --- a/testssl.sh +++ b/testssl.sh @@ -1229,7 +1229,8 @@ out_row_aligned_max_width() { local -i max_width="$3" local -i i len local cr=$'\n' - local line first=true + local line + local first=true max_width=$max_width-${#spaces} len=${#text} From 7953bfda5e3501fdcf9c47ef0fa59f2982aed402 Mon Sep 17 00:00:00 2001 From: Dirk Date: Wed, 29 Mar 2017 11:17:24 +0200 Subject: [PATCH 03/27] correct DEBUGTIME --- testssl.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testssl.sh b/testssl.sh index a1c854c..9bdcb7c 100755 --- a/testssl.sh +++ b/testssl.sh @@ -81,13 +81,13 @@ readonly PS4='|${LINENO}> \011${FUNCNAME[0]:+${FUNCNAME[0]}(): }' # see stackoverflow.com/questions/5014823/how-to-profile-a-bash-shell-script-slow-startup#20855353 # how to paste both in order to do performance analysis -DEBUGTIME=${DEBUGTIME:-false} -DEBUG_ALLINONE=${DEBUG_ALLINONE:-false} # true: do debugging in one sceen +DEBUGTIME=${DEBUGTIME:-false} +DEBUG_ALLINONE=${DEBUG_ALLINONE:-false} # true: do debugging in one sceen (old behaviour for just debugging) if grep -q xtrace <<< "$SHELLOPTS"; then - if "$DEBUGTIME" ; then + if "$DEBUGTIME"; then # separate debugging, doesn't mess up the screen, $DEBUGTIME determines whether we also do performance analysis exec 42>&2 2> >(tee /tmp/testssl-$$.log | sed -u 's/^.*$/now/' | date -f - +%s.%N >/tmp/testssl-$$.time) - BASH_XTRACEFD=42 + # BASH_XTRACEFD=42 else if ! "$DEBUG_ALLINONE"; then exec 42>| /tmp/testssl-$$.log From 86c81f227699f231afe8f606888f983009bc458b Mon Sep 17 00:00:00 2001 From: David Cooper Date: Wed, 29 Mar 2017 11:16:09 -0400 Subject: [PATCH 04/27] Use CHILD_MASS_TESTING environment variable This PR introduces the environment variable `CHILD_MASS_TESTING`, and uses it as an indicator that testssl.sh is running as a child within mass testing rather than using the `$APPEND` flag. It also makes a number of other changes to make the handling, of HTML, CSV, JSON, and log files consistent, and it fixes a number of bugs related to the generation of these files when mass testing is being performed. Please let me know if you disagree with any of the changes in this PR, or if you would prefer that it be broken up into multiple smaller PRs. Some of the changes are as follows: - When the `$APPEND` flag is true, all of these files are appended to and headers and footers are omitted. (Perhaps this should be changed. Appending to a log file isn't an issue, but appending to a JSON or HTML file without including headers or footers seems to just create an improperly formatted file). - Following the code in `prepare_logging()`, an error is printed and the program stops if the `$APPEND` flag is false and one of the files to be written to already exists. Some of the bugs fixed: Creating log files did not work with mass testing: - If `--logfile ` is used, then the parent and each child try to write to "logfile". - If `--logging` is used, then a log file is created for each child, but an oddly-named log file is also created for the parent. The one created by the parent contains the entire output. Plain JSON files: - When `--jsonfile ` is run, there is no comma separating the final finding for one child and the first finding for the next child. Pretty JSON files: - When `--jsonfile-pretty ` is called without mass testing, the "target host" line is empty, since `$NODE` has not yet been set. - When `--jsonfile ` is run with mass testing, there is no comma separating the final finding for one child and the first finding for the next child. In addition, `fileout_pretty_json_banner()` is never called, and the entries for individual tests have insufficient information to determine what is being tested (it lists "service" and "ip", but not port number). For the final issue, when mass testing is being performed and all output is being placed in a single file, I have the parent call `fileout_pretty_json_banner()`, but tell `fileout_pretty_json_banner()` to not include a "target host" or "port", but then have each child include a "target host" or "port" (when the "service" and "ip" are being printed). --- testssl.sh | 224 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 136 insertions(+), 88 deletions(-) diff --git a/testssl.sh b/testssl.sh index 9bdcb7c..8ef0e1a 100755 --- a/testssl.sh +++ b/testssl.sh @@ -186,6 +186,7 @@ GIVE_HINTS=false # give an addtional info to findings HAS_IPv6=${HAS_IPv6:-false} # if you have OpenSSL with IPv6 support AND IPv6 networking set it to yes UNBRACKTD_IPV6=${UNBRACKTD_IPV6:-false} # some versions of OpenSSL (like Gentoo) don't support [bracketed] IPv6 addresses SERVER_SIZE_LIMIT_BUG=false # Some servers have either a ClientHello total size limit or a 128 cipher limit (e.g. old ASAs) +CHILD_MASS_TESTING=${CHILD_MASS_TESTING:-false} # tuning vars, can not be set by a cmd line switch EXPERIMENTAL=${EXPERIMENTAL:-false} @@ -866,6 +867,8 @@ fileout_json_print_parameter() { } fileout_json_finding() { + local target + if "$do_json"; then "$FIRST_FINDING" || echo -n "," >> "$JSONFILE" echo -e " {" >> "$JSONFILE" @@ -884,9 +887,19 @@ fileout_json_finding() { if [[ $SERVER_COUNTER -gt 1 ]]; then echo " ," >> "$JSONFILE" fi - echo -e " { + if "$CHILD_MASS_TESTING" && ! "$JSONHEADER"; then + target="$NODE" + $do_mx_all_ips && target="$URI" + echo -e " { + \"target host\" : \"$target\", + \"port\" : \"$PORT\" \"service\" : \"$finding\", \"ip\" : \"$NODEIP\"," >> "$JSONFILE" + else + echo -e " { + \"service\" : \"$finding\", + \"ip\" : \"$NODEIP\"," >> "$JSONFILE" + fi $do_mx_all_ips && echo -e " \"hostname\" : \"$NODE\"," >> "$JSONFILE" else ("$FIRST_FINDING" && echo -n " {" >> "$JSONFILE") || echo -n ",{" >> "$JSONFILE" @@ -905,20 +918,33 @@ fileout_json_finding() { ##################### FILE FORMATING ######################### fileout_pretty_json_banner() { - echo -e " \"Invocation\" : \"$PROG_NAME $CMDLINE\", - \"at\" : \"$HNAME:$OPENSSL_LOCATION\", - \"version\" : \"$VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE\", - \"openssl\" : \"$OSSL_VER from $OSSL_BUILD_DATE\", - \"target host\" : \"$target\", - \"port\" : \"$PORT\", - \"startTime\" : \"$START_TIME\", - \"scanResult\" : [" + local target + + if "$do_mass_testing"; then + echo -e " \"Invocation\" : \"$PROG_NAME $CMDLINE\", + \"at\" : \"$HNAME:$OPENSSL_LOCATION\", + \"version\" : \"$VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE\", + \"openssl\" : \"$OSSL_VER from $OSSL_BUILD_DATE\", + \"startTime\" : \"$START_TIME\", + \"scanResult\" : [" + else + [[ -z "$NODE" ]] && parse_hn_port "${URI}" + # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now --> wrong place + target="$NODE" + $do_mx_all_ips && target="$URI" + + echo -e " \"Invocation\" : \"$PROG_NAME $CMDLINE\", + \"at\" : \"$HNAME:$OPENSSL_LOCATION\", + \"version\" : \"$VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE\", + \"openssl\" : \"$OSSL_VER from $OSSL_BUILD_DATE\", + \"target host\" : \"$target\", + \"port\" : \"$PORT\", + \"startTime\" : \"$START_TIME\", + \"scanResult\" : [" + fi } fileout_banner() { - local target="$NODE" - $do_mx_all_ips && target="$URI" - #if ! "$APPEND"; then # if "$CSVHEADER"; then # : @@ -956,18 +982,18 @@ fileout() { json_header() { local fname_prefix + local filename_provided=false + + [[ -n "$JSONFILE" ]] && [[ ! -d "$JSONFILE" ]] && filename_provided=true # Similar to HTML: Don't create headers and footers in the following scenarios: # * no JSON/CSV output is being created. # * mass testing is being performed and each test will have its own file. # * this is an individual test within a mass test and all output is being placed in a single file. + ! "$do_json" && ! "$do_pretty_json" && JSONHEADER=false && return 0 + "$do_mass_testing" && ! "$filename_provided" && JSONHEADER=false && return 0 + "$CHILD_MASS_TESTING" && "$filename_provided" && JSONHEADER=false && return 0 - if ( ! "$do_json" && ! "$do_pretty_json" ) || \ - ( "$do_mass_testing" && ( [[ -z "$JSONFILE" ]] || [[ -d "$JSONFILE" ]] ) ) || \ - ( "$APPEND" && [[ -n "$JSONFILE" ]] && [[ ! -d "$JSONFILE" ]] ); then - JSONHEADER=false - return 0 - fi if "$do_display_only"; then fname_prefix="local-ciphers" elif "$do_mass_testing"; then @@ -975,19 +1001,22 @@ json_header() { elif "$do_mx_all_ips"; then fname_prefix="mx-$URI" else - ( [[ -z "$JSONFILE" ]] || [[ -d "$JSONFILE" ]] ) && parse_hn_port "${URI}" + ! "$filename_provided" && [[ -z "$NODE" ]] && parse_hn_port "${URI}" # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now --> wrong place fname_prefix="${NODE}"_p"${PORT}" fi - if [[ -n "$JSONFILE" ]] && [[ ! -d "$JSONFILE" ]]; then - rm -f "$JSONFILE" - elif [[ -z "$JSONFILE" ]]; then + if [[ -z "$JSONFILE" ]]; then JSONFILE=$fname_prefix-$(date +"%Y%m%d-%H%M".json) - else + elif [[ -d "$JSONFILE" ]]; then JSONFILE=$JSONFILE/$fname_prefix-$(date +"%Y%m%d-%H%M".json) fi - "$do_json" && printf "[\n" > "$JSONFILE" - "$do_pretty_json" && printf "{\n" > "$JSONFILE" + if "$APPEND"; then + JSONHEADER=false + else + [[ -e "$JSONFILE" ]] && fatal "\"$JSONFILE\" exists. Either use \"--append\" or (re)move it" 1 + "$do_json" && printf "[\n" > "$JSONFILE" + "$do_pretty_json" && printf "{\n" > "$JSONFILE" + fi #FIRST_FINDING=false return 0 } @@ -995,14 +1024,15 @@ json_header() { csv_header() { local fname_prefix + local filename_provided=false + + [[ -n "$CSVFILE" ]] && [[ ! -d "$CSVFILE" ]] && filename_provided=true # CSV similar: - if ! "$do_csv" || \ - ( "$do_mass_testing" && ( [[ -z "$CSVFILE" ]] || [[ -d "$CSVFILE" ]] ) ) || \ - ( "$APPEND" && [[ -n "$CSVFILE" ]] && [[ ! -d "$CSVFILE" ]] ); then - CSVHEADER=false - return 0 - fi + ! "$do_csv" && CSVHEADER=false && return 0 + "$do_mass_testing" && ! "$filename_provided" && CSVHEADER=false && return 0 + "$CHILD_MASS_TESTING" && "$filename_provided" && CSVHEADER=false && return 0 + if "$do_display_only"; then fname_prefix="local-ciphers" elif "$do_mass_testing"; then @@ -1010,18 +1040,22 @@ csv_header() { elif "$do_mx_all_ips"; then fname_prefix="mx-$URI" else - ( [[ -z "$CSVFILE" ]] || [[ -d "$CSVFILE" ]] ) && parse_hn_port "${URI}" + ! "$filename_provided" && [[ -z "$NODE" ]] && parse_hn_port "${URI}" # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now --> wrong place fname_prefix="${NODE}"_p"${PORT}" fi - if [[ -n "$CSVFILE" ]] && [[ ! -d "$CSVFILE" ]]; then - rm -f "$CSVFILE" - elif [[ -z "$CSVFILE" ]]; then + + if [[ -z "$CSVFILE" ]]; then CSVFILE=$fname_prefix-$(date +"%Y%m%d-%H%M".csv) - else + elif [[ -d "$CSVFILE" ]]; then CSVFILE=$CSVFILE/$fname_prefix-$(date +"%Y%m%d-%H%M".csv) fi - "$do_csv" && echo "\"id\",\"fqdn/ip\",\"port\",\"severity\",\"finding\",\"cve\",\"cwe\",\"hint\"" > "$CSVFILE" + if "$APPEND"; then + CSVHEADER=false + else + [[ -e "$CSVFILE" ]] && fatal "\"$CSVFILE\" exists. Either use \"--append\" or (re)move it" 1 + echo "\"id\",\"fqdn/ip\",\"port\",\"severity\",\"finding\",\"cve\",\"cwe\",\"hint\"" > "$CSVFILE" + fi return 0 } @@ -1030,17 +1064,17 @@ csv_header() { html_header() { local fname_prefix + local filename_provided=false + + [[ -n "$HTMLFILE" ]] && [[ ! -d "$HTMLFILE" ]] && filename_provided=true # Don't create HTML headers and footers in the following scenarios: # * HTML output is not being created. # * mass testing is being performed and each test will have its own HTML file. # * this is an individual test within a mass test and all HTML output is being placed in a single file. - if ! "$do_html" || \ - ( "$do_mass_testing" && ( [[ -z "$HTMLFILE" ]] || [[ -d "$HTMLFILE" ]] ) ) || \ - ( "$APPEND" && [[ -n "$HTMLFILE" ]] && [[ ! -d "$HTMLFILE" ]] ); then - HTMLHEADER=false - return 0 - fi + ! "$do_html" && HTMLHEADER=false && return 0 + "$do_mass_testing" && ! "$filename_provided" && HTMLHEADER=false && return 0 + "$CHILD_MASS_TESTING" && "$filename_provided" && HTMLHEADER=false && return 0 if "$do_display_only"; then fname_prefix="local-ciphers" @@ -1049,33 +1083,36 @@ html_header() { elif "$do_mx_all_ips"; then fname_prefix="mx-$URI" else - ( [[ -z "$HTMLFILE" ]] || [[ -d "$HTMLFILE" ]] ) && parse_hn_port "${URI}" + ! "$filename_provided" && [[ -z "$NODE" ]] && parse_hn_port "${URI}" # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now --> wrong place fname_prefix="${NODE}"_p"${PORT}" fi - if [[ -n "$HTMLFILE" ]] && [[ ! -d "$HTMLFILE" ]]; then - rm -f "$HTMLFILE" - elif [[ -z "$HTMLFILE" ]]; then + if [[ -z "$HTMLFILE" ]]; then HTMLFILE=$fname_prefix-$(date +"%Y%m%d-%H%M".html) - else + elif [[ -d "$HTMLFILE" ]]; then HTMLFILE=$HTMLFILE/$fname_prefix-$(date +"%Y%m%d-%H%M".html) fi - html_out "\n" - html_out "\n" - html_out "\n" - html_out "\n" - html_out "\n" - html_out "\n" - html_out "testssl.sh\n" - html_out "\n" - html_out "\n" - html_out "
\n"
+     if "$APPEND"; then
+          HTMLHEADER=false
+     else
+          [[ -e "$HTMLFILE" ]] && fatal "\"$HTMLFILE\" exists. Either use \"--append\" or (re)move it" 1
+          html_out "\n"
+          html_out "\n"
+          html_out "\n"
+          html_out "\n"
+          html_out "\n"
+          html_out "\n"
+          html_out "testssl.sh\n"
+          html_out "\n"
+          html_out "\n"
+          html_out "
\n"
+     fi
      return 0
 }
 
 html_banner() {
-     if "$APPEND" && "$HTMLHEADER"; then
+     if "$CHILD_MASS_TESTING" && "$HTMLHEADER"; then
           html_out "## Scan started as: \"$PROG_NAME $CMDLINE\"\n"
           html_out "## at $HNAME:$OPENSSL_LOCATION\n"
           html_out "## version testssl: $VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE\n"
@@ -10946,7 +10983,8 @@ mybanner() {
      local openssl_location="$(which $OPENSSL)"
      local cwd=""
 
-     $QUIET && return
+     "$QUIET" && return
+     "$CHILD_MASS_TESTING" && return
      OPENSSL_NR_CIPHERS=$(count_ciphers "$($OPENSSL ciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>/dev/null)")
      [[ -z "$GIT_REL" ]] && \
           idtag="$CVS_REL" || \
@@ -11133,30 +11171,34 @@ parse_hn_port() {
 # arg1: for testing mx records name we put a name of logfile in here, otherwise we get strange file names
 prepare_logging() {
      local fname_prefix="$1"
+     local filename_provided=false
+
+     [[ -n "$LOGFILE" ]] && [[ ! -d "$LOGFILE" ]] && filename_provided=true
+
+     # Similar to html_header():
+     ! "$do_logging" && return 0
+     "$do_mass_testing" && ! "$filename_provided" && return 0
+     "$CHILD_MASS_TESTING" && "$filename_provided" && return 0
 
      [[ -z "$fname_prefix" ]] && fname_prefix="${NODE}"_p"${PORT}"
 
-     if "$do_logging"; then
-          if [[ -z "$LOGFILE" ]]; then
-               LOGFILE=$fname_prefix-$(date +"%Y%m%d-%H%M".log)
-          elif [[ -d "$LOGFILE" ]]; then
-               # actually we were instructed to place all files in a DIR instead of the current working dir
-               LOGFILE=$LOGFILE/$fname_prefix-$(date +"%Y%m%d-%H%M".log)
-          else
-               : # just for clarity: a log file was specified, no need to do anything else
-          fi
-
-          if ! "$APPEND"; then
-               [[ -e $LOGFILE ]] && fatal "\"$LOGFILE\" exists. Either use \"--append\" or (re)move it" 1
-          else
-               >$LOGFILE
-          fi
-          tmln_out "## Scan started as: \"$PROG_NAME $CMDLINE\"" >>${LOGFILE}
-          tmln_out "## at $HNAME:$OPENSSL_LOCATION" >>${LOGFILE}
-          tmln_out "## version testssl: $VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE" >>${LOGFILE}
-          tmln_out "## version openssl: \"$OSSL_VER\" from \"$OSSL_BUILD_DATE\")\n" >>${LOGFILE}
-          exec > >(tee -a ${LOGFILE})
+     if [[ -z "$LOGFILE" ]]; then
+          LOGFILE=$fname_prefix-$(date +"%Y%m%d-%H%M".log)
+     elif [[ -d "$LOGFILE" ]]; then
+          # actually we were instructed to place all files in a DIR instead of the current working dir
+          LOGFILE=$LOGFILE/$fname_prefix-$(date +"%Y%m%d-%H%M".log)
+     else
+          : # just for clarity: a log file was specified, no need to do anything else
      fi
+
+     if ! "$APPEND"; then
+          [[ -e $LOGFILE ]] && fatal "\"$LOGFILE\" exists. Either use \"--append\" or (re)move it" 1
+     fi
+     tmln_out "## Scan started as: \"$PROG_NAME $CMDLINE\"" >>${LOGFILE}
+     tmln_out "## at $HNAME:$OPENSSL_LOCATION" >>${LOGFILE}
+     tmln_out "## version testssl: $VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE" >>${LOGFILE}
+     tmln_out "## version openssl: \"$OSSL_VER\" from \"$OSSL_BUILD_DATE\")\n" >>${LOGFILE}
+     exec > >(tee -a ${LOGFILE})
 }
 
 
@@ -11777,11 +11819,11 @@ run_mass_testing_parallel() {
           cmdline=$(filter_input "$cmdline")
           [[ -z "$cmdline" ]] && continue
           [[ "$cmdline" == "EOF" ]] && break
-          cmdline="$0 $global_cmdline --warnings=batch -q $cmdline"
+          cmdline="$0 $global_cmdline --warnings=batch $cmdline"
           draw_line "=" $((TERM_WIDTH / 2)); outln;
           determine_logfile
           outln "$cmdline"
-          $cmdline >$LOGFILE &
+          CHILD_MASS_TESTING=true $cmdline >$LOGFILE &
           sleep $PARALLEL_SLEEP
      done < "$FNAME"
      return $?
@@ -11790,6 +11832,7 @@ run_mass_testing_parallel() {
 
 run_mass_testing() {
      local cmdline=""
+     local first=true
      local global_cmdline=${CMDLINE%%--file*}
 
      if [[ ! -r "$FNAME" ]] && "$IKNOW_FNAME"; then
@@ -11797,15 +11840,19 @@ run_mass_testing() {
      fi
 
      pr_reverse "====== Running in file batch mode with file=\"$FNAME\" ======"; outln "\n"
-     APPEND=false # Make sure we close out our files
      while read cmdline; do
           cmdline=$(filter_input "$cmdline")
           [[ -z "$cmdline" ]] && continue
           [[ "$cmdline" == "EOF" ]] && break
-          cmdline="$0 $global_cmdline --warnings=batch -q --append $cmdline"
+          cmdline="$0 $global_cmdline --warnings=batch $cmdline"
           draw_line "=" $((TERM_WIDTH / 2)); outln;
           outln "$cmdline"
-          $cmdline
+          if ! "$first" && "$JSONHEADER"; then
+               "$do_pretty_json" && echo "          ," >> "$JSONFILE"
+               "$do_json" && echo -n "," >> "$JSONFILE"
+          fi
+          CHILD_MASS_TESTING=true $cmdline
+          first=false
      done < "${FNAME}"
      return $?
 }
@@ -12453,19 +12500,20 @@ check_proxy
 check4openssl_oldfarts
 check_bsd_mount
 
-if $do_display_only; then
+if "$do_display_only"; then
      prettyprint_local "$PATTERN2SHOW"
      exit $?
 fi
 
-if $do_mass_testing; then
+fileout_banner
+
+if "$do_mass_testing"; then
      prepare_logging
      run_mass_testing
      exit $?
 fi
 
 html_banner
-fileout_banner
 
 #TODO: there shouldn't be the need for a special case for --mx, only the ip adresses we would need upfront and the do-parser
 if $do_mx_all_ips; then

From 04f86f94697c03c5560705210b11898e4baf185d Mon Sep 17 00:00:00 2001
From: David Cooper 
Date: Wed, 29 Mar 2017 11:22:29 -0400
Subject: [PATCH 05/27] Fix indentation of JSON pretty banner

---
 testssl.sh | 36 ++++++++++++++++++------------------
 1 file changed, 18 insertions(+), 18 deletions(-)

diff --git a/testssl.sh b/testssl.sh
index 8ef0e1a..7963bf6 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -921,26 +921,26 @@ fileout_pretty_json_banner() {
      local target
 
      if "$do_mass_testing"; then
-          echo -e "          \"Invocation\"  : \"$PROG_NAME $CMDLINE\",
-               \"at\"          : \"$HNAME:$OPENSSL_LOCATION\",
-               \"version\"     : \"$VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE\",
-               \"openssl\"     : \"$OSSL_VER from $OSSL_BUILD_DATE\",
-               \"startTime\"   : \"$START_TIME\",
-               \"scanResult\"  : ["
+        echo -e "          \"Invocation\"  : \"$PROG_NAME $CMDLINE\",
+          \"at\"          : \"$HNAME:$OPENSSL_LOCATION\",
+          \"version\"     : \"$VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE\",
+          \"openssl\"     : \"$OSSL_VER from $OSSL_BUILD_DATE\",
+          \"startTime\"   : \"$START_TIME\",
+          \"scanResult\"  : ["
      else
-          [[ -z "$NODE" ]] && parse_hn_port "${URI}"
-          # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now  --> wrong place
-          target="$NODE"
-          $do_mx_all_ips && target="$URI"
+        [[ -z "$NODE" ]] && parse_hn_port "${URI}"
+        # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now  --> wrong place
+        target="$NODE"
+        $do_mx_all_ips && target="$URI"
 
-          echo -e "          \"Invocation\"  : \"$PROG_NAME $CMDLINE\",
-               \"at\"          : \"$HNAME:$OPENSSL_LOCATION\",
-               \"version\"     : \"$VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE\",
-               \"openssl\"     : \"$OSSL_VER from $OSSL_BUILD_DATE\",
-               \"target host\" : \"$target\",
-               \"port\"        : \"$PORT\",
-               \"startTime\"   : \"$START_TIME\",
-               \"scanResult\"  : ["
+        echo -e "          \"Invocation\"  : \"$PROG_NAME $CMDLINE\",
+          \"at\"          : \"$HNAME:$OPENSSL_LOCATION\",
+          \"version\"     : \"$VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE\",
+          \"openssl\"     : \"$OSSL_VER from $OSSL_BUILD_DATE\",
+          \"target host\" : \"$target\",
+          \"port\"        : \"$PORT\",
+          \"startTime\"   : \"$START_TIME\",
+          \"scanResult\"  : ["
      fi
 }
 

From e7c0ca13f673935672d1082ff5c70e4d6b30f17d Mon Sep 17 00:00:00 2001
From: David Cooper 
Date: Wed, 29 Mar 2017 11:41:23 -0400
Subject: [PATCH 06/27] Remove tmp.json files after use

Remove tmp.json files are use so that testssl.sh doesn't complain that they already exist.
---
 t/01_badssl.com.t | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/t/01_badssl.com.t b/t/01_badssl.com.t
index 8c91e07..01ca476 100755
--- a/t/01_badssl.com.t
+++ b/t/01_badssl.com.t
@@ -16,6 +16,7 @@ my (
 pass("Running testssl.sh against badssl.com to create a baseline (may take 2~3 minutes)"); $tests++;
 my $okout = `./testssl.sh -S -e -U --jsonfile tmp.json --color 0 badssl.com`;
 my $okjson = json('tmp.json');
+unlink 'tmp.json';
 cmp_ok(@$okjson,'>',10,"We have more then 10 findings"); $tests++;
 
 # Expiration
@@ -23,6 +24,7 @@ pass("Running testssl against expired.badssl.com"); $tests++;
 $out = `./testssl.sh -S --jsonfile tmp.json --color 0 expired.badssl.com`;
 like($out, qr/Certificate Expiration\s+expired\!/,"The certificate should be expired"); $tests++;
 $json = json('tmp.json');
+unlink 'tmp.json';
 $found = 0;
 foreach my $f ( @$json ) {
 	if ( $f->{id} eq "expiration" ) {
@@ -39,6 +41,7 @@ pass("Running testssl against self-signed.badssl.com"); $tests++;
 $out = `./testssl.sh -S --jsonfile tmp.json --color 0 self-signed.badssl.com`;
 like($out, qr/Certificate Expiration\s+\d+/,"The certificate should not be expired"); $tests++;
 $json = json('tmp.json');
+unlink 'tmp.json';
 $found = 0;
 foreach my $f ( @$json ) {
 	if ( $f->{id} eq "expiration" ) {
@@ -79,6 +82,7 @@ is($found,1,"We had a finding for this in the JSON output"); $tests++;
 #$out = `./testssl.sh -S --jsonfile tmp.json --color 0 wrong.host.badssl.com`;
 #unlike($out, qr/Certificate Expiration\s+expired\!/,"The certificate should not be expired"); $tests++;
 #$json = json('tmp.json');
+#unlink 'tmp.json';
 #$found = 0;
 #foreach my $f ( @$json ) {
 #	if ( $f->{id} eq "expiration" ) {
@@ -95,6 +99,7 @@ pass("Running testssl against incomplete-chain.badssl.com"); $tests++;
 $out = `./testssl.sh -S --jsonfile tmp.json --color 0 incomplete-chain.badssl.com`;
 like($out, qr/Chain of trust.*?NOT ok\s+\(chain incomplete\)/,"Chain of trust should fail because of incomplete"); $tests++;
 $json = json('tmp.json');
+unlink 'tmp.json';
 $found = 0;
 foreach my $f ( @$json ) {
 	if ( $f->{id} eq "chain_of_trust" ) {
@@ -113,6 +118,7 @@ is($found,1,"We had a finding for this in the JSON output"); $tests++;
 #$out = `./testssl.sh -e -U --jsonfile tmp.json --color 0 cbc.badssl.com`;
 #like($out, qr/Chain of trust.*?NOT ok\s+\(chain incomplete\)/,"Chain of trust should fail because of incomplete"); $tests++;
 #$json = json('tmp.json');
+#unlink 'tmp.json';
 #$found = 0;
 #foreach my $f ( @$json ) {
 #	if ( $f->{id} eq "chain_of_trust" ) {
@@ -132,4 +138,4 @@ sub json($) {
 	$file = `cat $file`;
 	unlink $file;
 	return from_json($file);
-}
\ No newline at end of file
+}

From 172337451150d0aa1bcb3f0a1d98b8886fd4aa45 Mon Sep 17 00:00:00 2001
From: David Cooper 
Date: Wed, 29 Mar 2017 11:42:09 -0400
Subject: [PATCH 07/27] Remove tmp.file files after use

Remove tmp.json files are use so that testssl.sh doesn't complain that they already exist.
---
 t/100_report_structure.t | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/t/100_report_structure.t b/t/100_report_structure.t
index 5fdfb47..b933b4b 100644
--- a/t/100_report_structure.t
+++ b/t/100_report_structure.t
@@ -19,6 +19,7 @@ $tests = 0;
 pass("Running testssl.sh against badssl.com to create a JSON report with severity level equal greater than LOW (may take 2~3 minutes)"); $tests++;
 $out = `./testssl.sh -S -e -U --jsonfile tmp.json --severity LOW --color 0 badssl.com`;
 $json = json('tmp.json');
+unlink 'tmp.json';
 $found = 0;
 cmp_ok(@$json,'>',0,"At least 1 finding is expected"); $tests++;
 foreach my $f ( @$json ) {
@@ -33,6 +34,7 @@ is($found,0,"We should not have any finding with INFO level"); $tests++;
 pass("Running testssl.sh against badssl.com to create a JSON-PRETTY report with severity level equal greater than LOW (may take 2~3 minutes)"); $tests++;
 $out = `./testssl.sh -S -e -U --jsonfile-pretty tmp.json --severity LOW --color 0 badssl.com`;
 $json_pretty = json('tmp.json');
+unlink 'tmp.json';
 $found = 0;
 my $vulnerabilities = $json_pretty->{scanResult}->[0]->{vulnerabilities};
 foreach my $f ( @$vulnerabilities ) {
@@ -50,4 +52,4 @@ sub json($) {
     $file = `cat $file`;
     unlink $file;
     return from_json($file);
-}
\ No newline at end of file
+}

From 603f03e79a76289df74057bdd8f5ffad29b68cca Mon Sep 17 00:00:00 2001
From: David Cooper 
Date: Wed, 29 Mar 2017 11:43:03 -0400
Subject: [PATCH 08/27] Remove tmp.json files after use

Remove tmp.json files after use so that testssl.sh doesn't complain that they already exist.
---
 t/11_hpkp.t | 1 +
 1 file changed, 1 insertion(+)

diff --git a/t/11_hpkp.t b/t/11_hpkp.t
index b701558..dc5464b 100755
--- a/t/11_hpkp.t
+++ b/t/11_hpkp.t
@@ -16,6 +16,7 @@ my (
 pass("Running testssl.sh against ssl.sectionzero.org"); $tests++;
 $out = `./testssl.sh -H --jsonfile tmp.json --color 0 ssl.sectionzero.org`;
 $json = json('tmp.json');
+unlink 'tmp.json';
 
 # It is better to have findings in a hash
 # Look for a host cert match in the process.

From ba2a75b09395d83dbf81dd0b62b25acc321d9432 Mon Sep 17 00:00:00 2001
From: David Cooper 
Date: Thu, 30 Mar 2017 10:08:26 -0400
Subject: [PATCH 09/27] Cleanup variable definitions in run_server_defaults()

In `run_server_defaults()` the variable `success` is defined twice, once an an ordinary variable and once as an array. The PR removes the incorrect definition. It also removes the definitions of some variables that are no longer used and reorganizes the definitions so that each line has only one variable type.

I also noticed a typo later in `run_server_defaults()` and corrected it.
---
 testssl.sh | 14 ++++++--------
 1 file changed, 6 insertions(+), 8 deletions(-)

diff --git a/testssl.sh b/testssl.sh
index 9bdcb7c..c24286b 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -6080,18 +6080,16 @@ certificate_info() {
 
 
 run_server_defaults() {
-     local ciph match_found newhostcert sni
-     local sessticket_str=""
-     local lifetime unit
-     local line
+     local ciph newhostcert sni
+     local match_found
+     local sessticket_str="" lifetime unit
      local -i i n
      local -i certs_found=0
      local -a previous_hostcert previous_intermediates keysize cipher
      local -a ocsp_response ocsp_response_status sni_used
-     local -a ciphers_to_test success
+     local -a ciphers_to_test
+     local -a -i success
      local cn_nosni cn_sni sans_nosni sans_sni san tls_extensions
-     local alpn_proto alpn="" alpn_list_len_hex alpn_extn_len_hex success
-     local -i alpn_list_len alpn_extn_len
 
      # Try each public key type once:
      # ciphers_to_test[1]: cipher suites using certificates with RSA signature public keys
@@ -6249,7 +6247,7 @@ run_server_defaults() {
           unit=$(grep -a lifetime <<< "$sessticket_str" | sed -e 's/^.*'"$lifetime"'//' -e 's/[ ()]//g')
           out "$lifetime $unit "
           prln_svrty_low "(PFS requires session ticket keys to be rotated <= daily)"
-          fileout "session_ticket" "LOW" "TLS session tickes RFC 5077 valid for $lifetime $unit (PFS requires session ticket keys to be rotated at least daily)"
+          fileout "session_ticket" "LOW" "TLS session ticket RFC 5077 valid for $lifetime $unit (PFS requires session ticket keys to be rotated at least daily)"
      fi
 
      pr_bold " SSL Session ID support       "

From 73a24cba27366a335ac6749e798981e4b6e896c2 Mon Sep 17 00:00:00 2001
From: David Cooper 
Date: Thu, 30 Mar 2017 10:27:08 -0400
Subject: [PATCH 10/27] Correct indentation in run_server_defaults()

This second commit doesn't make any changes to the code, it just corrects the indentation.
---
 testssl.sh | 182 +++++++++++++++++++++++++++--------------------------
 1 file changed, 92 insertions(+), 90 deletions(-)

diff --git a/testssl.sh b/testssl.sh
index c24286b..5917122 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -6102,11 +6102,11 @@ run_server_defaults() {
      ciphers_to_test[1]=""
      ciphers_to_test[2]=""
      for ciph in $(colon_to_spaces $($OPENSSL ciphers "aRSA")); do
-         if grep -q "\-RSA\-" <<<$ciph; then
-             ciphers_to_test[1]="${ciphers_to_test[1]}:$ciph"
-         else
-             ciphers_to_test[2]="${ciphers_to_test[2]}:$ciph"
-         fi
+          if grep -q "\-RSA\-" <<<$ciph; then
+               ciphers_to_test[1]="${ciphers_to_test[1]}:$ciph"
+          else
+               ciphers_to_test[2]="${ciphers_to_test[2]}:$ciph"
+          fi
      done
      [[ -n "${ciphers_to_test[1]}" ]] && ciphers_to_test[1]="${ciphers_to_test[1]:1}"
      [[ -n "${ciphers_to_test[2]}" ]] && ciphers_to_test[2]="${ciphers_to_test[2]:1}"
@@ -6117,94 +6117,96 @@ run_server_defaults() {
      ciphers_to_test[7]="aGOST"
 
      for (( n=1; n <= 14 ; n++ )); do
-         # Some servers use a different certificate if the ClientHello
-         # specifies TLSv1.1 and doesn't include a server name extension.
-         # So, for each public key type for which a certificate was found,
-         # try again, but only with TLSv1.1 and without SNI.
-         if [[ $n -ge 8 ]]; then
-              ciphers_to_test[n]=""
-              [[ ${success[n-7]} -eq 0 ]] && ciphers_to_test[n]="${ciphers_to_test[n-7]}"
-         fi
+          # Some servers use a different certificate if the ClientHello
+          # specifies TLSv1.1 and doesn't include a server name extension.
+          # So, for each public key type for which a certificate was found,
+          # try again, but only with TLSv1.1 and without SNI.
+          if [[ $n -ge 8 ]]; then
+               ciphers_to_test[n]=""
+               [[ ${success[n-7]} -eq 0 ]] && ciphers_to_test[n]="${ciphers_to_test[n-7]}"
+          fi
 
-         if [[ -n "${ciphers_to_test[n]}" ]] && [[ $(count_ciphers $($OPENSSL ciphers "${ciphers_to_test[n]}" 2>>$ERRFILE)) -ge 1 ]]; then
-             if [[ $n -ge 8 ]]; then
-                  sni="$SNI"
-                  SNI=""
-                  get_server_certificate "-cipher ${ciphers_to_test[n]}" "tls1_1"
-                  success[n]=$?
-                  SNI="$sni"
-             else
-                  get_server_certificate "-cipher ${ciphers_to_test[n]}"
-                  success[n]=$?
-             fi
-             if [[ ${success[n]} -eq 0 ]]; then
-                 cp "$TEMPDIR/$NODEIP.get_server_certificate.txt" $TMPFILE
-                 >$ERRFILE
-                 if [[ -z "$sessticket_str" ]]; then
-                     sessticket_str=$(grep -aw "session ticket" $TMPFILE | grep -a lifetime)
-                 fi
+          if [[ -n "${ciphers_to_test[n]}" ]] && [[ $(count_ciphers $($OPENSSL ciphers "${ciphers_to_test[n]}" 2>>$ERRFILE)) -ge 1 ]]; then
+               if [[ $n -ge 8 ]]; then
+                    sni="$SNI"
+                    SNI=""
+                    get_server_certificate "-cipher ${ciphers_to_test[n]}" "tls1_1"
+                    success[n]=$?
+                    SNI="$sni"
+               else
+                    get_server_certificate "-cipher ${ciphers_to_test[n]}"
+                    success[n]=$?
+               fi
+               if [[ ${success[n]} -eq 0 ]]; then
+                    cp "$TEMPDIR/$NODEIP.get_server_certificate.txt" $TMPFILE
+                    >$ERRFILE
+                    if [[ -z "$sessticket_str" ]]; then
+                         sessticket_str=$(grep -aw "session ticket" $TMPFILE | grep -a lifetime)
+                    fi
 
-                 # check whether the host's certificate has been seen before
-                 match_found=false
-                 i=1
-                 newhostcert=$(cat $HOSTCERT)
-                 while [[ $i -le $certs_found ]]; do
-                     if [[ "$newhostcert" == "${previous_hostcert[i]}" ]]; then
-                        match_found=true
-                        break;
-                     fi
-                     i=$((i + 1))
-                 done
-                 if ! "$match_found" && [[ $n -ge 8 ]] && [[ $certs_found -ne 0 ]]; then
-                     # A new certificate was found using TLSv1.1 without SNI.
-                     # Check to see if the new certificate should be displayed.
-                     # It should be displayed if it is either a match for the
-                     # $NODE being tested or if it has the same subject
-                     # (CN and SAN) as other certificates for this host.
-                     compare_server_name_to_cert "$NODE" "$HOSTCERT"
-                     [[ $? -ne 0 ]] && success[n]=0 || success[n]=1
+                    # check whether the host's certificate has been seen before
+                    match_found=false
+                    i=1
+                    newhostcert=$(cat $HOSTCERT)
+                    while [[ $i -le $certs_found ]]; do
+                         if [[ "$newhostcert" == "${previous_hostcert[i]}" ]]; then
+                              match_found=true
+                              break;
+                         fi
+                         i=$((i + 1))
+                    done
+                    if ! "$match_found" && [[ $n -ge 8 ]] && [[ $certs_found -ne 0 ]]; then
+                         # A new certificate was found using TLSv1.1 without SNI.
+                         # Check to see if the new certificate should be displayed.
+                         # It should be displayed if it is either a match for the
+                         # $NODE being tested or if it has the same subject
+                         # (CN and SAN) as other certificates for this host.
+                         compare_server_name_to_cert "$NODE" "$HOSTCERT"
+                         [[ $? -ne 0 ]] && success[n]=0 || success[n]=1
 
-                     if [[ ${success[n]} -ne 0 ]]; then
-                         cn_nosni="$(toupper "$(get_cn_from_cert $HOSTCERT)")"
-                         sans_nosni="$(toupper "$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | grep -A2 "Subject Alternative Name" | \
-                              tr ',' '\n' |  grep "DNS:" | sed -e 's/DNS://g' -e 's/ //g' | tr '\n' ' ')")"
+                         if [[ ${success[n]} -ne 0 ]]; then
+                              cn_nosni="$(toupper "$(get_cn_from_cert $HOSTCERT)")"
+                              sans_nosni="$(toupper "$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | \
+                                   grep -A2 "Subject Alternative Name" | tr ',' '\n' | grep "DNS:" | \
+                                   sed -e 's/DNS://g' -e 's/ //g' | tr '\n' ' ')")"
 
-                         echo "${previous_hostcert[1]}" > $HOSTCERT
-                         cn_sni="$(toupper "$(get_cn_from_cert $HOSTCERT)")"
+                              echo "${previous_hostcert[1]}" > $HOSTCERT
+                              cn_sni="$(toupper "$(get_cn_from_cert $HOSTCERT)")"
 
-                         # FIXME: Not sure what the matching rule should be. At
-                         # the moment, the no SNI certificate is considered a
-                         # match if the CNs are the same and the SANs (if
-                         # present) contain at least one DNS name in common.
-                         if [[ "$cn_nosni" == "$cn_sni" ]]; then
-                              sans_sni="$(toupper "$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | grep -A2 "Subject Alternative Name" | \
-                                       tr ',' '\n' |  grep "DNS:" | sed -e 's/DNS://g' -e 's/ //g' | tr '\n' ' ')")"
-                              if [[ "$sans_nosni" == "$sans_sni" ]]; then
-                                   success[n]=0
-                              else
-                                   for san in $sans_nosni; do
-                                        [[ " $sans_sni " =~ " $san " ]] && success[n]=0 && break
-                                   done
+                              # FIXME: Not sure what the matching rule should be. At
+                              # the moment, the no SNI certificate is considered a
+                              # match if the CNs are the same and the SANs (if
+                              # present) contain at least one DNS name in common.
+                              if [[ "$cn_nosni" == "$cn_sni" ]]; then
+                                   sans_sni="$(toupper "$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | \
+                                        grep -A2 "Subject Alternative Name" | tr ',' '\n' | grep "DNS:" | \
+                                        sed -e 's/DNS://g' -e 's/ //g' | tr '\n' ' ')")"
+                                   if [[ "$sans_nosni" == "$sans_sni" ]]; then
+                                        success[n]=0
+                                   else
+                                        for san in $sans_nosni; do
+                                             [[ " $sans_sni " =~ " $san " ]] && success[n]=0 && break
+                                        done
+                                   fi
                               fi
                          fi
-                     fi
-                     # If the certificate found for TLSv1.1 w/o SNI appears to
-                     # be for a different host, then set match_found to true so
-                     # that the new certificate will not be included in the output.
-                     [[ ${success[n]} -ne 0 ]] && match_found=true
-                 fi
-                 if ! "$match_found"; then
-                     certs_found=$(($certs_found + 1))
-                     cipher[certs_found]=${ciphers_to_test[n]}
-                     keysize[certs_found]=$(grep -aw "^Server public key is" $TMPFILE | sed -e 's/^Server public key is //' -e 's/bit//' -e 's/ //')
-                     ocsp_response[certs_found]=$(grep -aA 20 "OCSP response" $TMPFILE)
-                     ocsp_response_status[certs_found]=$(grep -a "OCSP Response Status" $TMPFILE)
-                     previous_hostcert[certs_found]=$newhostcert
-                     previous_intermediates[certs_found]=$(cat $TEMPDIR/intermediatecerts.pem)
-                     [[ $n -ge 8 ]] && sni_used[certs_found]="" || sni_used[certs_found]="$SNI"
-                 fi
-             fi
-         fi
+                         # If the certificate found for TLSv1.1 w/o SNI appears to
+                         # be for a different host, then set match_found to true so
+                         # that the new certificate will not be included in the output.
+                         [[ ${success[n]} -ne 0 ]] && match_found=true
+                    fi
+                    if ! "$match_found"; then
+                         certs_found=$(($certs_found + 1))
+                         cipher[certs_found]=${ciphers_to_test[n]}
+                         keysize[certs_found]=$(grep -aw "^Server public key is" $TMPFILE | sed -e 's/^Server public key is //' -e 's/bit//' -e 's/ //')
+                         ocsp_response[certs_found]=$(grep -aA 20 "OCSP response" $TMPFILE)
+                         ocsp_response_status[certs_found]=$(grep -a "OCSP Response Status" $TMPFILE)
+                         previous_hostcert[certs_found]=$newhostcert
+                         previous_intermediates[certs_found]=$(cat $TEMPDIR/intermediatecerts.pem)
+                         [[ $n -ge 8 ]] && sni_used[certs_found]="" || sni_used[certs_found]="$SNI"
+                    fi
+               fi
+          fi
      done
 
      determine_tls_extensions
@@ -6263,10 +6265,10 @@ run_server_defaults() {
 
      i=1
      while [[ $i -le $certs_found ]]; do
-         echo "${previous_hostcert[i]}" > $HOSTCERT
-         echo "${previous_intermediates[i]}" > $TEMPDIR/intermediatecerts.pem
-         certificate_info "$i" "$certs_found" "${cipher[i]}" "${keysize[i]}" "${ocsp_response[i]}" "${ocsp_response_status[i]}" "${sni_used[i]}"
-         i=$((i + 1))
+          echo "${previous_hostcert[i]}" > $HOSTCERT
+          echo "${previous_intermediates[i]}" > $TEMPDIR/intermediatecerts.pem
+          certificate_info "$i" "$certs_found" "${cipher[i]}" "${keysize[i]}" "${ocsp_response[i]}" "${ocsp_response_status[i]}" "${sni_used[i]}"
+          i=$((i + 1))
      done
 }
 

From d8a70370006441660b05cf494f325762b00debff Mon Sep 17 00:00:00 2001
From: David Cooper 
Date: Thu, 30 Mar 2017 12:37:41 -0400
Subject: [PATCH 11/27] Add missing comma

I did some testing with http://jsonlint.com/ and discovered a missing comma when massing testing is being performed and a single JSON file is being created.
---
 testssl.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/testssl.sh b/testssl.sh
index 7963bf6..d009372 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -892,7 +892,7 @@ fileout_json_finding() {
                  $do_mx_all_ips && target="$URI"
                  echo -e "          {
                     \"target host\"     : \"$target\",
-                    \"port\"            : \"$PORT\"
+                    \"port\"            : \"$PORT\",
                     \"service\"         : \"$finding\",
                     \"ip\"              : \"$NODEIP\","  >> "$JSONFILE"
             else

From 9f93d9d578a8a34c695d983f1d0a9d7d140cdf5c Mon Sep 17 00:00:00 2001
From: David Cooper 
Date: Thu, 30 Mar 2017 12:48:25 -0400
Subject: [PATCH 12/27] Move insertion of commas to a separate file

Create a separate function to insert the comma separators between findings for different tests within mass testing.
---
 testssl.sh | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/testssl.sh b/testssl.sh
index d009372..4697baa 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -956,6 +956,13 @@ fileout_banner() {
      #fi
 }
 
+fileout_separator() {
+     if "$JSONHEADER"; then
+          "$do_pretty_json" && echo "          ," >> "$JSONFILE"
+          "$do_json" && echo -n "," >> "$JSONFILE"
+     fi
+}
+
 fileout_footer() {
      if "$JSONHEADER"; then
           fileout_json_footer
@@ -11847,10 +11854,7 @@ run_mass_testing() {
           cmdline="$0 $global_cmdline --warnings=batch $cmdline"
           draw_line "=" $((TERM_WIDTH / 2)); outln;
           outln "$cmdline"
-          if ! "$first" && "$JSONHEADER"; then
-               "$do_pretty_json" && echo "          ," >> "$JSONFILE"
-               "$do_json" && echo -n "," >> "$JSONFILE"
-          fi
+          "$first" || fileout_separator
           CHILD_MASS_TESTING=true $cmdline
           first=false
      done < "${FNAME}"

From a480e5f699983207651aa0a8717dc395d13e6e52 Mon Sep 17 00:00:00 2001
From: Dirk 
Date: Fri, 31 Mar 2017 12:24:25 +0200
Subject: [PATCH 13/27] count_ciphers is now un-sed'ed, minor improvements

---
 testssl.sh | 98 +++++++++++++++++++++++++++---------------------------
 1 file changed, 49 insertions(+), 49 deletions(-)

diff --git a/testssl.sh b/testssl.sh
index 4697baa..44cf79f 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -742,7 +742,7 @@ pr_url()     { tm_out "$1"; html_out "$1"; }
 
 ### color switcher (see e.g. https://linuxtidbits.wordpress.com/2008/08/11/output-color-on-bash-scripts/
-###                         http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
+###                          http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
 set_color_functions() {
      local ncurses_tput=true
 
@@ -845,7 +845,7 @@ fileout_section_header() {
     "$do_pretty_json" && FIRST_FINDING=true && (printf "%s%s\n" "$str" "$(fileout_json_section "$1")") >> "$JSONFILE"
 }
 
-fileout_section_footer() { 
+fileout_section_footer() {
     "$do_pretty_json" && printf "\n                    ]" >> "$JSONFILE"
     "$do_pretty_json" && "$1" && echo -e "\n          }" >> "$JSONFILE"
 }
@@ -1072,7 +1072,7 @@ csv_header() {
 html_header() {
      local fname_prefix
      local filename_provided=false
-     
+
      [[ -n "$HTMLFILE" ]] && [[ ! -d "$HTMLFILE" ]] && filename_provided=true
 
      # Don't create HTML headers and footers in the following scenarios:
@@ -1146,8 +1146,8 @@ if [[ $(uname) == "Linux" ]] ; then
      toupper() { echo -n "${1^^}" ;  }
      tolower() { echo -n "${1,,}" ;  }
 else
-     toupper() { echo -n "$1" | tr 'a-z' 'A-Z'; }
-     tolower() { echo -n "$1" | tr 'A-Z' 'a-z' ; }
+     toupper() { tr 'a-z' 'A-Z' <<< "$1"; }
+     tolower() { tr 'A-Z' 'a-z'  <<< "$1"; }
 fi
 
 debugme() {
@@ -1181,7 +1181,7 @@ count_words() {
 }
 
 count_ciphers() {
-     echo -n "$1" | sed 's/:/ /g' | wc -w | sed 's/ //g'
+     echo $(wc -w <<< "${1//:/ }")
 }
 
 actually_supported_ciphers() {
@@ -1476,7 +1476,6 @@ service_detection() {
           head $TMPFILE | egrep -aqw "Jive News|InterNetNews|NNRP|INN" && SERVICE=NNTP
           debugme head -50 $TMPFILE
      fi
-# FIXME: we can guess ports by port number if not properly recognized (and label it as guessed)
 
      out " Service detected:      $CORRECT_SPACES"
      case $SERVICE in
@@ -2494,7 +2493,7 @@ std_cipherlists() {
                     ;;
           esac
           tmpfile_handle $FUNCNAME.$debugname.txt
-          [[ $DEBUG -ge 1 ]] && outln " -- $1" || outln  #FIXME: should be in standard output at some time
+          [[ $DEBUG -ge 1 ]] && tmln_out " -- $1" || tmln_out
      else
           singlespaces=$(sed -e 's/ \+/ /g' -e 's/^ //' -e 's/ $//g' -e 's/  //g' <<< "$2")
           if [[ "$OPTIMAL_PROTO" == "-ssl2" ]]; then
@@ -10155,7 +10154,7 @@ run_beast(){
                fi
           else
                if ! "$vuln_beast" ; then
-                    prln_done_good " no CBC ciphers for $(toupper $proto) (OK)"
+                    prln_done_good "no CBC ciphers for $(toupper $proto) (OK)"
                     fileout "cbc_$proto" "OK" "BEAST: No CBC ciphers for $(toupper $proto)" "$cve" "$cwe"
                fi
           fi
@@ -10862,7 +10861,7 @@ EOF
 }
 
 maketempf() {
-     TEMPDIR=$(mktemp -d /tmp/ssltester.XXXXXX) || exit -6
+     TEMPDIR=$(mktemp -d /tmp/testssl.XXXXXX) || exit -6
      TMPFILE=$TEMPDIR/tempfile.txt || exit -6
      if [[ "$DEBUG" -eq 0 ]]; then
           ERRFILE="/dev/null"
@@ -10999,7 +10998,7 @@ mybanner() {
      bb1=$(cat <$LOGFILE &
+          # first=false
           sleep $PARALLEL_SLEEP
      done < "$FNAME"
      return $?
 }
 
 
-run_mass_testing() {
-     local cmdline=""
-     local first=true
-     local global_cmdline=${CMDLINE%%--file*}
-
-     if [[ ! -r "$FNAME" ]] && "$IKNOW_FNAME"; then
-          fatal "Can't read file \"$FNAME\"" "2"
-     fi
-
-     pr_reverse "====== Running in file batch mode with file=\"$FNAME\" ======"; outln "\n"
-     while read cmdline; do
-          cmdline=$(filter_input "$cmdline")
-          [[ -z "$cmdline" ]] && continue
-          [[ "$cmdline" == "EOF" ]] && break
-          cmdline="$0 $global_cmdline --warnings=batch $cmdline"
-          draw_line "=" $((TERM_WIDTH / 2)); outln;
-          outln "$cmdline"
-          "$first" || fileout_separator
-          CHILD_MASS_TESTING=true $cmdline
-          first=false
-     done < "${FNAME}"
-     return $?
-}
-
-
 
 # This initializes boolean global do_* variables. They keep track of what to do
 # -- as the name insinuates
@@ -12490,9 +12490,6 @@ ip=""
 lets_roll init
 initialize_globals
 parse_cmd_line "$@"
-json_header
-csv_header
-html_header
 get_install_dir
 set_color_functions
 maketempf
@@ -12503,6 +12500,9 @@ mybanner
 check_proxy
 check4openssl_oldfarts
 check_bsd_mount
+json_header
+csv_header
+html_header
 
 if "$do_display_only"; then
      prettyprint_local "$PATTERN2SHOW"

From 6b601e22c766ac61d4e23e4cf3a7c077f29f5fde Mon Sep 17 00:00:00 2001
From: Dirk 
Date: Fri, 31 Mar 2017 17:04:04 +0200
Subject: [PATCH 14/27] adding Referrer-Policy header (FIX #604)

introduced get_san_dns_from_cert()

added two stub function get_session_ticket_lifetime_from_serverhello
---
 testssl.sh | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/testssl.sh b/testssl.sh
index f7d51da..91bb5f3 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -2284,7 +2284,7 @@ run_cookie_flags() {     # ARG1: Path
 
 run_more_flags() {
      local good_flags2test="X-Frame-Options X-XSS-Protection X-Content-Type-Options Content-Security-Policy X-Content-Security-Policy X-WebKit-CSP Content-Security-Policy-Report-Only"
-     local other_flags2test="Access-Control-Allow-Origin Upgrade X-Served-By X-UA-Compatible"
+     local other_flags2test="Access-Control-Allow-Origin Upgrade X-Served-By X-UA-Compatible Referrer-Policy"
      local f2t
      local first=true
      local spaces="                              "
@@ -5477,7 +5477,7 @@ compare_server_name_to_cert()
 
      # If the CN contains any characters that are not valid for a DNS name,
      # then assume it does not contain a DNS name.
-     [[ -n $(echo -n "$cn" | sed 's/^[\.a-zA-Z0-9*\-]*//') ]] && return $ret
+     [[ -n $(sed 's/^[\.a-zA-Z0-9*\-]*//' <<< "$cn") ]] && return $ret
 
      # Check whether the CN in the certificate matches the servername
      [[ $(toupper "$cn") == "$servername" ]] && ret+=4 && return $ret
@@ -6241,7 +6241,7 @@ run_server_defaults() {
                     if ! "$match_found"; then
                          certs_found=$(($certs_found + 1))
                          cipher[certs_found]=${ciphers_to_test[n]}
-                         keysize[certs_found]=$(grep -aw "^Server public key is" $TMPFILE | sed -e 's/^Server public key is //' -e 's/bit//' -e 's/ //')
+                         keysize[certs_found]=$(awk '/Server public key/ { print $(NF-1) }' $TMPFILE)
                          ocsp_response[certs_found]=$(grep -aA 20 "OCSP response" $TMPFILE)
                          ocsp_response_status[certs_found]=$(grep -a "OCSP Response Status" $TMPFILE)
                          previous_hostcert[certs_found]=$newhostcert
@@ -6315,6 +6315,19 @@ run_server_defaults() {
      done
 }
 
+get_session_ticket_lifetime_from_serverhello() {
+     awk '/session ticket.*lifetime/ { print $(NF-1) "$1" }'
+}
+
+get_san_dns_from_cert() {
+     toupper "$($OPENSSL x509 -in "$1" -noout -text 2>>$ERRFILE | \
+          grep -A2 "Subject Alternative Name" | tr ',' '\n' | grep "DNS:" | \
+          sed -e 's/DNS://g' -e 's/ //g' | tr '\n' ' ')"
+}
+
+
+
+
 run_pfs() {
      local -i sclient_success
      local pfs_offered=false ecdhe_offered=false ffdhe_offered=false

From 498dda94ce1f8ed131c66bf426c96ceb15c293f3 Mon Sep 17 00:00:00 2001
From: Dirk 
Date: Sat, 1 Apr 2017 10:38:04 +0200
Subject: [PATCH 15/27] using get_san_dns_from_cert()

---
 testssl.sh | 15 +++++----------
 1 file changed, 5 insertions(+), 10 deletions(-)

diff --git a/testssl.sh b/testssl.sh
index 91bb5f3..39d8b26 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -5449,8 +5449,7 @@ compare_server_name_to_cert()
      local -i ret=0
 
      # Check whether any of the DNS names in the certificate match the servername
-     dns_sans=$($OPENSSL x509 -in "$cert" -noout -text 2>>$ERRFILE | grep -A2 "Subject Alternative Name" | \
-               tr ',' '\n' |  grep "DNS:" | sed -e 's/DNS://g' -e 's/ //g')
+     dns_sans="$(get_san_dns_from_cert "$cert")"
      for san in $dns_sans; do
           [[ $(toupper "$san") == "$servername" ]] && ret=1 && break
      done
@@ -5495,7 +5494,7 @@ must_staple() {
      local cert extn
      local -i extn_len
      local supported=false
-     
+
      # Note this function is only looking for status_request (5) and not
      # status_request_v2 (17), since OpenSSL seems to only include status_request (5)
      # in its ClientHello when the "-status" option is used.
@@ -6209,9 +6208,7 @@ run_server_defaults() {
 
                          if [[ ${success[n]} -ne 0 ]]; then
                               cn_nosni="$(toupper "$(get_cn_from_cert $HOSTCERT)")"
-                              sans_nosni="$(toupper "$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | \
-                                   grep -A2 "Subject Alternative Name" | tr ',' '\n' | grep "DNS:" | \
-                                   sed -e 's/DNS://g' -e 's/ //g' | tr '\n' ' ')")"
+                              sans_nosni="$(toupper "$(get_san_dns_from_cert "$HOSTCERT")")"
 
                               echo "${previous_hostcert[1]}" > $HOSTCERT
                               cn_sni="$(toupper "$(get_cn_from_cert $HOSTCERT)")"
@@ -6221,9 +6218,7 @@ run_server_defaults() {
                               # match if the CNs are the same and the SANs (if
                               # present) contain at least one DNS name in common.
                               if [[ "$cn_nosni" == "$cn_sni" ]]; then
-                                   sans_sni="$(toupper "$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | \
-                                        grep -A2 "Subject Alternative Name" | tr ',' '\n' | grep "DNS:" | \
-                                        sed -e 's/DNS://g' -e 's/ //g' | tr '\n' ' ')")"
+                                   sans_sni="$(toupper "$(get_san_dns_from_cert "$HOSTCERT")")"
                                    if [[ "$sans_nosni" == "$sans_sni" ]]; then
                                         success[n]=0
                                    else
@@ -6320,7 +6315,7 @@ get_session_ticket_lifetime_from_serverhello() {
 }
 
 get_san_dns_from_cert() {
-     toupper "$($OPENSSL x509 -in "$1" -noout -text 2>>$ERRFILE | \
+     echo "$($OPENSSL x509 -in "$1" -noout -text 2>>$ERRFILE | \
           grep -A2 "Subject Alternative Name" | tr ',' '\n' | grep "DNS:" | \
           sed -e 's/DNS://g' -e 's/ //g' | tr '\n' ' ')"
 }

From 8213e2436cc041bd6341ad634de79222526dd2dd Mon Sep 17 00:00:00 2001
From: Dirk 
Date: Tue, 4 Apr 2017 09:54:47 +0200
Subject: [PATCH 16/27] addressed #691 for 2.9dev

---
 testssl.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/testssl.sh b/testssl.sh
index 39d8b26..c9a7ff0 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -11252,7 +11252,7 @@ get_local_aaaa() {
      local etchosts="/etc/hosts /c/Windows/System32/drivers/etc/hosts"
 
      # for security testing sometimes we have local entries. Getent is BS under Linux for localhost: No network, no resolution
-     ip6=$(grep -wh "$1" $etchosts 2>/dev/null | grep ':' | egrep -v '^#|\.local' | egrep "[[:space:]]$1" | awk '{ print $1 }')
+     ip6=$(grep -wih "$1" $etchosts 2>/dev/null | grep ':' | egrep -v '^#|\.local' | egrep -i "[[:space:]]$1" | awk '{ print $1 }')
      if is_ipv6addr "$ip6"; then
           echo "$ip6"
      else
@@ -11265,7 +11265,7 @@ get_local_a() {
      local etchosts="/etc/hosts /c/Windows/System32/drivers/etc/hosts"
 
      # for security testing sometimes we have local entries. Getent is BS under Linux for localhost: No network, no resolution
-     ip4=$(grep -wh "$1" $etchosts 2>/dev/null | egrep -v ':|^#|\.local' |  egrep "[[:space:]]$1" | awk '{ print $1 }')
+     ip4=$(grep -wih "$1" $etchosts 2>/dev/null | egrep -v ':|^#|\.local' | egrep -i "[[:space:]]$1" | awk '{ print $1 }')
      if is_ipv4addr "$ip4"; then
           echo "$ip4"
      else

From 1b4c1cc40c48dc3a581ac57762b6ae37b4b7530f Mon Sep 17 00:00:00 2001
From: David Cooper 
Date: Tue, 4 Apr 2017 13:25:31 -0400
Subject: [PATCH 17/27] Update testssl.sh

This PR fixes two issues with HTML generation that were introduced by a commit on March 31, 2016, "[count_ciphers is now un-sed'ed, minor improvements](https://github.com/drwetter/testssl.sh/commit/a480e5f699983207651aa0a8717dc395d13e6e52)."

The first is that in `std_cipherlists()`, `[[ $DEBUG -ge 1 ]] && outln " -- $1" || outln` was changed to `[[ $DEBUG -ge 1 ]] && outln " -- $1" || outln`. The result being that in the HTML output, all of the tests from `run_std_cipherlists()` appear on the same line. This PR changes the line to:
```
          [[ $DEBUG -ge 1 ]] && tm_out " -- $1"
          outln
``
so that the line break is added to the HTML output, but the debugging information is not.

The second problem is that the commit on March 31 moved the call in main to `html_header()` until after the calls to `get_install_dir()`, `find_openssl_binary()`, `mybanner()`, `check4openssl_oldfarts()`, and `check_bsd_mount()`. The problem is that each of these functions may call an output function that will call `html_out()`.

If `html_out()` is called before `html_header()` and the command line contains `--htmlfile `, then "htmlfile" will be written to before `html_header()` is called and then `html_header()` will warn that "htmlfile" already exists and then exit the program.

If `html_out()` is called before `html_header()` and the command line contains `--html`, then anything send to `html_out()` before `html_header()` is called (such as the banner) will not appear in the HTML file.
---
 testssl.sh | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/testssl.sh b/testssl.sh
index c9a7ff0..38f5174 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -2493,7 +2493,8 @@ std_cipherlists() {
                     ;;
           esac
           tmpfile_handle $FUNCNAME.$debugname.txt
-          [[ $DEBUG -ge 1 ]] && tmln_out " -- $1" || tmln_out
+          [[ $DEBUG -ge 1 ]] && tm_out " -- $1"
+          outln
      else
           singlespaces=$(sed -e 's/ \+/ /g' -e 's/^ //' -e 's/ $//g' -e 's/  //g' <<< "$2")
           if [[ "$OPTIMAL_PROTO" == "-ssl2" ]]; then
@@ -12498,6 +12499,7 @@ ip=""
 lets_roll init
 initialize_globals
 parse_cmd_line "$@"
+html_header
 get_install_dir
 set_color_functions
 maketempf
@@ -12510,7 +12512,6 @@ check4openssl_oldfarts
 check_bsd_mount
 json_header
 csv_header
-html_header
 
 if "$do_display_only"; then
      prettyprint_local "$PATTERN2SHOW"

From 7549f10c792fd4434f6a9f6fa8bc07fa696bfabc Mon Sep 17 00:00:00 2001
From: Dirk 
Date: Tue, 4 Apr 2017 20:23:28 +0200
Subject: [PATCH 18/27] added explanation for #692

---
 testssl.sh | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/testssl.sh b/testssl.sh
index 38f5174..207cfbc 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -12499,7 +12499,12 @@ ip=""
 lets_roll init
 initialize_globals
 parse_cmd_line "$@"
+# html_header() needs to be called early! Otherwiseif  html_out() is called before html_header() and the
+# command line contains --htmlfile  or --html, it'll make problems with html output, see #692.
+# json_header and csv_header can be called later but for context reasons we'll leave it here
 html_header
+json_header
+csv_header
 get_install_dir
 set_color_functions
 maketempf
@@ -12510,8 +12515,6 @@ mybanner
 check_proxy
 check4openssl_oldfarts
 check_bsd_mount
-json_header
-csv_header
 
 if "$do_display_only"; then
      prettyprint_local "$PATTERN2SHOW"

From 6b0f3892253be1ecb835852d9d781948d824fdd6 Mon Sep 17 00:00:00 2001
From: Dirk 
Date: Wed, 5 Apr 2017 14:42:55 +0200
Subject: [PATCH 19/27] fix #694 (CSP and HTTP header friends were cut off @
 last colon)

introduced strip_leading_space() / strip_trailing_space()
---
 testssl.sh | 34 +++++++++++++++++++++-------------
 1 file changed, 21 insertions(+), 13 deletions(-)

diff --git a/testssl.sh b/testssl.sh
index 207cfbc..e4f8562 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -1204,9 +1204,14 @@ strip_spaces() {
      echo "${1// /}"
 }
 
-trim_trailing_space() {
-     echo "${1%%*( )}"
+# https://web.archive.org/web/20121022051228/http://codesnippets.joyent.com/posts/show/1816
+strip_leading_space() {
+     echo "${1#"${1%%[\![:space:]]*}"}"
 }
+strip_trailing_space() {
+     echo "${1%"${1##*[![:space:]]}"}"
+}
+
 
 # retrieve cipher from ServerHello (via openssl)
 get_cipher() {
@@ -1714,7 +1719,9 @@ detect_header() {
           HEADERVALUE=""
           return 0
      elif [[ $nr -eq 1 ]]; then
-          HEADERVALUE=$(grep -Faiw "$key:" $HEADERFILE | sed 's/^.*://')
+          HEADERVALUE=$(grep -Faiw "$key:" $HEADERFILE)
+          HEADERVALUE=${HEADERVALUE#*:}                        # remove leading part=key to colon
+          HEADERVALUE="$(strip_leading_space "$HEADERVALUE")"
           return 1
      else
           pr_svrty_medium "misconfiguration: "
@@ -1722,15 +1729,14 @@ detect_header() {
           pr_svrty_medium " ${nr}x"
           out " -- checking first one "
           out "\n$spaces"
-          # first awk matches the key, second extracts the from the first line the value, be careful with quotes here!
-          HEADERVALUE=$(grep -Faiw "$key:" $HEADERFILE | sed 's/^.*://' | head -1)
+          HEADERVALUE=$(grep -Faiw "$key:" $HEADERFILE | head -1)
+          HEADERVALUE=${HEADERVALUE#*:}
+          HEADERVALUE="$(strip_leading_space "$HEADERVALUE")"
           [[ $DEBUG -ge 2 ]] && tm_italic "$HEADERVALUE" && tm_out "\n$spaces"
           fileout "$2""_multiple" "WARN" "Multiple $2 headers. Using first header: $HEADERVALUE"
           return $nr
      fi
 }
-# wir brauchen hier eine Funktion, die generell den Header detectiert
-
 
 includeSubDomains() {
      if grep -aiqw includeSubDomains "$1"; then
@@ -2296,28 +2302,30 @@ run_more_flags() {
      pr_bold " Security headers             "
      for f2t in $good_flags2test; do
           debugme echo "---> $f2t"
-          detect_header $f2t $f2t
+          detect_header "$f2t" "$f2t"
           if [[ $? -ge 1 ]]; then
                if ! "$first"; then
-                    out "$spaces"  # output leading spaces if the first header
+                    out "$spaces"       # output leading spaces if the first header
                else
                     first=false
                fi
-               pr_done_good "$f2t"; outln "$HEADERVALUE"
+               pr_done_good "$f2t"
+               outln "$(out_row_aligned_max_width "$HEADERVALUE" "                              " $TERM_WIDTH)"
                fileout "$f2t" "OK" "$f2t: $HEADERVALUE"
           fi
      done
 
      for f2t in $other_flags2test; do
           debugme echo "---> $f2t"
-          detect_header $f2t $f2t
+          detect_header "$f2t" "$f2t"
           if [[ $? -ge 1 ]]; then
                if ! "$first"; then
-                    out "$spaces"  # output leading spaces if the first header
+                    out "$spaces"       # output leading spaces if the first header
                else
                     first=false
                fi
-               pr_litecyan "$f2t"; outln "$HEADERVALUE"
+               pr_litecyan "$f2t"
+               outln "$HEADERVALUE"     # shouldn't be that long
                fileout "$f2t" "WARN" "$f2t: $HEADERVALUE"
           fi
      done

From b1ce11d76e17c0dc656e2be1a097250d9dd4ee48 Mon Sep 17 00:00:00 2001
From: Dirk 
Date: Wed, 5 Apr 2017 14:48:35 +0200
Subject: [PATCH 20/27] in addition to #694: using the predefined variable

---
 testssl.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/testssl.sh b/testssl.sh
index e4f8562..3101a8e 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -2310,7 +2310,7 @@ run_more_flags() {
                     first=false
                fi
                pr_done_good "$f2t"
-               outln "$(out_row_aligned_max_width "$HEADERVALUE" "                              " $TERM_WIDTH)"
+               outln "$(out_row_aligned_max_width "$HEADERVALUE" "$spaces" $TERM_WIDTH)"
                fileout "$f2t" "OK" "$f2t: $HEADERVALUE"
           fi
      done

From bfb0f4bc7d6abac726acefccab51ac315cf337ec Mon Sep 17 00:00:00 2001
From: Dirk 
Date: Wed, 5 Apr 2017 17:28:06 +0200
Subject: [PATCH 21/27] FIX #697 in 2.9dev (bash hiccup @ tolower)

---
 testssl.sh | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/testssl.sh b/testssl.sh
index 3101a8e..7325d40 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -1142,12 +1142,12 @@ html_footer() {
 
 ###### START helper function definitions ######
 
-if [[ $(uname) == "Linux" ]] ; then
-     toupper() { echo -n "${1^^}" ;  }
-     tolower() { echo -n "${1,,}" ;  }
-else
+toupper() { echo -n "${1^^}" ;  }
+tolower() { echo -n "${1,,}" ;  }
+if ! toupper aaa 2>/dev/null; then
+     # Older bash can't do this (MacOS X), even SLES 11, see #697
      toupper() { tr 'a-z' 'A-Z' <<< "$1"; }
-     tolower() { tr 'A-Z' 'a-z'  <<< "$1"; }
+     tolower() { tr 'A-Z' 'a-z' <<< "$1"; }
 fi
 
 debugme() {

From ec55cdea141ea9522abfa9d1754057c6be2eb394 Mon Sep 17 00:00:00 2001
From: Dirk 
Date: Wed, 5 Apr 2017 20:20:00 +0200
Subject: [PATCH 22/27] "post-fix" for #697 (2.9dev)

---
 testssl.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/testssl.sh b/testssl.sh
index 7325d40..eb0e13a 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -1144,7 +1144,7 @@ html_footer() {
 
 toupper() { echo -n "${1^^}" ;  }
 tolower() { echo -n "${1,,}" ;  }
-if ! toupper aaa 2>/dev/null; then
+if ! toupper aaa 2>&1 >/dev/null; then
      # Older bash can't do this (MacOS X), even SLES 11, see #697
      toupper() { tr 'a-z' 'A-Z' <<< "$1"; }
      tolower() { tr 'A-Z' 'a-z' <<< "$1"; }
@@ -11215,7 +11215,7 @@ prepare_logging() {
      fi
 
      if ! "$APPEND"; then
-          [[ -e $LOGFILE ]] && fatal "\"$LOGFILE\" exists. Either use \"--append\" or (re)move it" 1
+          [[ -e $LOGFILE ]] && outln && fatal "\"$LOGFILE\" exists. Either use \"--append\" or (re)move it" 1
      fi
      tmln_out "## Scan started as: \"$PROG_NAME $CMDLINE\"" >>${LOGFILE}
      tmln_out "## at $HNAME:$OPENSSL_LOCATION" >>${LOGFILE}

From 61d42b022ca6bc5de68c5589255bd75a337d0a2c Mon Sep 17 00:00:00 2001
From: Dirk 
Date: Wed, 5 Apr 2017 20:39:35 +0200
Subject: [PATCH 23/27] fix missing space in banner and suppress empty version
 string

---
 testssl.sh | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/testssl.sh b/testssl.sh
index eb0e13a..873e224 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -11015,7 +11015,7 @@ mybanner() {
      bb1=$(cat <
Date: Wed, 5 Apr 2017 16:58:57 -0400
Subject: [PATCH 24/27] Mass testing in parallel

This PR modifies `run_mass_testing_parallel()` so that it may be used in place of  `run_mass_testing()`.
---
 testssl.sh | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 150 insertions(+), 8 deletions(-)

diff --git a/testssl.sh b/testssl.sh
index 873e224..2d42894 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -296,6 +296,15 @@ HEX_CIPHER=""
 
 SERVER_COUNTER=0                        # Counter for multiple servers
 
+########### Global variables for parallel mass testing
+readonly PARALLEL_SLEEP=1                 # Time to sleep after starting each test
+readonly MAX_WAIT_TEST=600                # Maximum time to wait for a test to complete
+readonly MAX_PARALLEL=20                  # Maximum number of tests to run in parallel
+declare -a -i PARALLEL_TESTING_PID=()     # process id for each child test
+declare -a PARALLEL_TESTING_CMDLINE=()    # command line for each child test
+declare -i NR_PARALLEL_TESTS=0            # number of parallel tests run
+declare -i NEXT_PARALLEL_TEST_TO_FINISH=0 # number of parallel tests that have completed and have been processed
+
 #################### SEVERITY ####################
 INFO=0
 OK=0
@@ -11064,6 +11073,20 @@ EOF
 
 
 cleanup () {
+     # If parallel mass testing is being performed, then the child tests need
+     # to be killed before $TEMPDIR is deleted. Otherwise, error messages
+     # will be created if testssl.sh is stopped before all testing is complete.
+     while [[ $NEXT_PARALLEL_TEST_TO_FINISH -lt $NR_PARALLEL_TESTS ]]; do
+          if ps ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} >/dev/null ; then
+               kill ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} >&2 2>/dev/null
+               wait ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} 2>/dev/null    # make sure pid terminated, see wait(1p)
+          else
+               # If a test had already completed, but its output wasn't yet processed,
+               # then process it now.
+               get_next_message_testing_parallel_result
+          fi
+          NEXT_PARALLEL_TEST_TO_FINISH+=1
+     done
      if [[ "$DEBUG" -ge 1 ]]; then
           tmln_out
           tm_underline "DEBUG (level $DEBUG): see files in $TEMPDIR"
@@ -11855,30 +11878,149 @@ run_mass_testing() {
      return $?
 }
 
+# Modify global command line for child tests in mass processing.
+# In particular if all (JSON, CSV, HTML) output is to go into a single
+# file, then have each child place its output in a separate, named file, so
+# that the separate files can be concatenated together once they are complete
+# to create the single file.
+modify_global_cmd_line() {
+     local global_cmdline="" filename
+     local ret
+
+     while [[ $# -gt 0 ]]; do
+          case "$1" in
+               --jsonfile|--jsonfile=*)
+                    filename=$(parse_opt_equal_sign "$1" "$2")
+                    ret=$?
+                    # If  is a file, then have provide a different
+                    # file name to each child process. If  is a
+                    # directory, then just pass it on to the child processes.
+                    if "$JSONHEADER"; then
+                         global_cmdline+="--jsonfile=$TEMPDIR/jsonfile_XXXXXXXX.json "
+                    else
+                         global_cmdline+="$1 "
+                         [[ $ret -eq 0 ]] && global_cmdline+="$2 "
+                    fi
+                    [[ $ret -eq 0 ]] && shift
+                    ;;     
+               --jsonfile-pretty|--jsonfile-pretty=*)
+                    filename=$(parse_opt_equal_sign "$1" "$2")
+                    ret=$?
+                    # Same as for --jsonfile
+                    if "$JSONHEADER"; then
+                         global_cmdline+="--jsonfile-pretty=$TEMPDIR/jsonfile_XXXXXXXX.json "
+                    else
+                         global_cmdline+="$1 "
+                         [[ $ret -eq 0 ]] && global_cmdline+="$2 "
+                    fi
+                    [[ $ret -eq 0 ]] && shift
+                    ;;               
+               --csvfile|--csvfile=*)
+                    filename=$(parse_opt_equal_sign "$1" "$2")
+                    ret=$?
+                    # Same as for --jsonfile
+                    if "$CSVHEADER"; then
+                         global_cmdline+="--csvfile=$TEMPDIR/csvfile_XXXXXXXX.csv "
+                    else
+                         global_cmdline+="$1 "
+                         [[ $ret -eq 0 ]] && global_cmdline+="$2 "
+                    fi
+                    [[ $ret -eq 0 ]] && shift
+                    ;;
+               --htmlfile|--htmlfile=*)
+                    filename=$(parse_opt_equal_sign "$1" "$2")
+                    ret=$?
+                    # Same as for --jsonfile
+                    if "$HTMLHEADER"; then
+                         global_cmdline+="--htmlfile=$TEMPDIR/htmlfile_XXXXXXXX.html "
+                    else
+                         global_cmdline+="$1 "
+                         [[ $ret -eq 0 ]] && global_cmdline+="$2 "
+                    fi
+                    [[ $ret -eq 0 ]] && shift
+                    ;;
+               *)
+                    global_cmdline+="$1 "
+                    ;;
+          esac
+          shift
+     done
+     tm_out "$global_cmdline"
+     return 0
+}
+
+# This function is called when it has been determined that the next child process has completed.
+# This process prints its output to the terminal and, if appropriate, adds any JSON, CSV, and HTML
+# output it has created to the appropriate file.
+get_next_message_testing_parallel_result() {
+     draw_line "=" $((TERM_WIDTH / 2)); outln;
+     outln "${PARALLEL_TESTING_CMDLINE[NEXT_PARALLEL_TEST_TO_FINISH]}"
+     cat "$TEMPDIR/term_output_$(printf "%08d" $NEXT_PARALLEL_TEST_TO_FINISH).log"
+     [[ $NEXT_PARALLEL_TEST_TO_FINISH -gt 0 ]] && fileout_separator                     # this is needed for appended output, see #687
+     "$JSONHEADER" && cat "$TEMPDIR/jsonfile_$(printf "%08d" $NEXT_PARALLEL_TEST_TO_FINISH).json" >> "$JSONFILE"
+     "$CSVHEADER" && cat "$TEMPDIR/csvfile_$(printf "%08d" $NEXT_PARALLEL_TEST_TO_FINISH).csv" >> "$CSVFILE"
+     "$HTMLHEADER" && cat "$TEMPDIR/htmlfile_$(printf "%08d" $NEXT_PARALLEL_TEST_TO_FINISH).html" >> "$HTMLFILE"
+}
 
 #FIXME: not called/tested yet
 run_mass_testing_parallel() {
      local cmdline=""
-     local first=true
+     local one_jsonfile=false
      local global_cmdline=${CMDLINE%%--file*}
 
      if [[ ! -r "$FNAME" ]] && $IKNOW_FNAME; then
           fatal "Can't read file \"$FNAME\"" "2"
      fi
-     pr_reverse "====== Running in parallel file batch mode with file=\"$FNAME\" ======"; outln
-     outln "(output is in ....\n)"
-#FIXME: once this function is being called we need a handler which does the right thing, i.e.  ==> not to overwrite
+     global_cmdline="$(modify_global_cmd_line $global_cmdline)"
+     [[ "$global_cmdline" =~ jsonfile_XXXXXXXX ]] && one_jsonfile=true
+     
+     pr_reverse "====== Running in parallel file batch mode with file=\"$FNAME\" ======"; outln "\n"
      while read cmdline; do
           cmdline=$(filter_input "$cmdline")
           [[ -z "$cmdline" ]] && continue
           [[ "$cmdline" == "EOF" ]] && break
           cmdline="$0 $global_cmdline --warnings=batch $cmdline"
-          draw_line "=" $((TERM_WIDTH / 2)); outln;
-          outln "$cmdline"
-          CHILD_MASS_TESTING=true $cmdline >$LOGFILE &
-          # first=false
+
+          # If the JSON, CSV, or HTML output is to all go into a single file, then replace the
+          # generic "_XXXXXXXX" filenames with different names for each child process, by
+          # replacing "XXXXXXXX" with the test's number
+          cmdline="${cmdline/jsonfile_XXXXXXXX\.json/jsonfile_$(printf "%08d" $NR_PARALLEL_TESTS).json}"
+
+          # fileout() won't include the "service" information in the JSON file for the child process
+          # if the JSON file doesn't already exist.
+          "$one_jsonfile" && echo -n "" > "$TEMPDIR/jsonfile_$(printf "%08d" $NR_PARALLEL_TESTS).json"
+          cmdline="${cmdline/csvfile_XXXXXXXX\.csv/csvfile_$(printf "%08d" $NR_PARALLEL_TESTS).csv}"
+          cmdline="${cmdline/htmlfile_XXXXXXXX\.html/htmlfile_$(printf "%08d" $NR_PARALLEL_TESTS).html}"
+          PARALLEL_TESTING_CMDLINE[NR_PARALLEL_TESTS]="$cmdline"
+          CHILD_MASS_TESTING=true $cmdline > "$TEMPDIR/term_output_$(printf "%08d" $NR_PARALLEL_TESTS).log" &
+          PARALLEL_TESTING_PID[NR_PARALLEL_TESTS]=$!
+          NR_PARALLEL_TESTS+=1
           sleep $PARALLEL_SLEEP
+          # Get the results of any completed tests
+          while [[ $NEXT_PARALLEL_TEST_TO_FINISH -lt $NR_PARALLEL_TESTS ]]; do
+               if ! ps ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} >/dev/null ; then
+                    get_next_message_testing_parallel_result
+                    NEXT_PARALLEL_TEST_TO_FINISH+=1
+               else
+                    break
+               fi
+          done
+          if [[ $NR_PARALLEL_TESTS-$NEXT_PARALLEL_TEST_TO_FINISH -ge $MAX_PARALLEL ]]; then
+               # The maximum number of tests that may be run in parallel has
+               # been reached. Need to wait for one to finish before starting
+               # another.
+               wait_kill ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} $MAX_WAIT_TEST
+               [[ $? -eq 0 ]] && get_next_message_testing_parallel_result
+               NEXT_PARALLEL_TEST_TO_FINISH+=1
+          fi
      done < "$FNAME"
+
+     # Wait for remaining tests to finish
+     while [[ $NEXT_PARALLEL_TEST_TO_FINISH -lt $NR_PARALLEL_TESTS ]]; do
+          wait_kill ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} $MAX_WAIT_TEST
+          [[ $? -eq 0 ]] && get_next_message_testing_parallel_result
+          NEXT_PARALLEL_TEST_TO_FINISH+=1
+     done
      return $?
 }
 

From 8a2967c62e9fa74bf045dfb39e5f297603e1f676 Mon Sep 17 00:00:00 2001
From: Dirk 
Date: Thu, 6 Apr 2017 09:47:09 +0200
Subject: [PATCH 25/27] make use of swapped out tls data file

(main() sill needs a bit of work)
---
 testssl.sh | 386 ++++++++++++-----------------------------------------
 1 file changed, 86 insertions(+), 300 deletions(-)

diff --git a/testssl.sh b/testssl.sh
index 873e224..cf1482e 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -108,9 +108,10 @@ egrep -q "dev|rc" <<< "$VERSION" && \
 
 readonly PROG_NAME=$(basename "$0")
 readonly RUN_DIR=$(dirname "$0")
-TESTSSL_INSTALL_DIR="${TESTSSL_INSTALL_DIR:-""}"   # if you run testssl.sh from a different path you can set either TESTSSL_INSTALL_DIR
-CA_BUNDLES_PATH="${CA_BUNDLES_PATH:-""}"           # or CA_BUNDLES_PATH to find the CA BUNDLES. TESTSSL_INSTALL_DIR helps you to find the RFC mapping also
+TESTSSL_INSTALL_DIR="${TESTSSL_INSTALL_DIR:-""}"  # if you run testssl.sh from a different path you can set either TESTSSL_INSTALL_DIR
+CA_BUNDLES_PATH="${CA_BUNDLES_PATH:-""}"          # or CA_BUNDLES_PATH to find the CA BUNDLES. TESTSSL_INSTALL_DIR helps you to find the RFC mapping also
 CIPHERS_BY_STRENGTH_FILE=""
+TLS_DATA_FILE=""                                  # mandatory file for socket based handdhakes
 OPENSSL_LOCATION=""
 HNAME="$(hostname)"
 HNAME="${HNAME%%.*}"
@@ -297,6 +298,7 @@ HEX_CIPHER=""
 SERVER_COUNTER=0                        # Counter for multiple servers
 
 #################### SEVERITY ####################
+
 INFO=0
 OK=0
 LOW=1
@@ -337,237 +339,6 @@ show_finding() {
 }
 
 
-###### some hexbytes for bash network sockets follow ######
-
-# 133 standard cipher + 4x GOST for TLS 1.2 and SPDY/NPN
-readonly TLS12_CIPHER="
-cc,14, cc,13, cc,15, c0,30, c0,2c, c0,28, c0,24, c0,14,
-c0,0a, c0,22, c0,21, c0,20, 00,a5, 00,a3, 00,a1, 00,9f,
-00,6b, 00,6a, 00,69, 00,68, 00,39, 00,38, 00,37, 00,36, 00,80, 00,81, 00,82, 00,83,
-c0,77, c0,73, 00,c4, 00,c3, 00,c2, 00,c1, 00,88, 00,87,
-00,86, 00,85, c0,32, c0,2e, c0,2a, c0,26, c0,0f, c0,05,
-c0,79, c0,75, 00,9d, 00,3d, 00,35, 00,c0, 00,84, c0,2f,
-c0,2b, c0,27, c0,23, c0,13, c0,09, c0,1f, c0,1e, c0,1d,
-00,a4, 00,a2, 00,a0, 00,9e, 00,67, 00,40, 00,3f, 00,3e,
-00,33, 00,32, 00,31, 00,30, c0,76, c0,72, 00,be, 00,bd,
-00,bc, 00,bb, 00,9a, 00,99, 00,98, 00,97, 00,45, 00,44,
-00,43, 00,42, c0,31, c0,2d, c0,29, c0,25, c0,0e, c0,04,
-c0,78, c0,74, 00,9c, 00,3c, 00,2f, 00,ba, 00,96, 00,41,
-00,07, c0,11, c0,07, 00,66, c0,0c, c0,02, 00,05, 00,04,
-c0,12, c0,08, c0,1c, c0,1b, c0,1a, 00,16, 00,13, 00,10,
-00,0d, c0,0d, c0,03, 00,0a, 00,63, 00,15, 00,12, 00,0f,
-00,0c, 00,62, 00,09, 00,65, 00,64, 00,14, 00,11, 00,0e,
-00,0b, 00,08, 00,06, 00,03, 00,ff"
-
-# 76 standard cipher +4x GOST for SSLv3, TLS 1, TLS 1.1
-readonly TLS_CIPHER="
-c0,14, c0,0a, c0,22, c0,21, c0,20, 00,39, 00,38, 00,37,
-00,36, 00,88, 00,87, 00,86, 00,85, c0,0f, c0,05, 00,35,
-00,84, c0,13, c0,09, c0,1f, c0,1e, c0,1d, 00,33, 00,32, 00,80, 00,81, 00,82, 00,83,
-00,31, 00,30, 00,9a, 00,99, 00,98, 00,97, 00,45, 00,44,
-00,43, 00,42, c0,0e, c0,04, 00,2f, 00,96, 00,41, 00,07,
-c0,11, c0,07, 00,66, c0,0c, c0,02, 00,05, 00,04, c0,12,
-c0,08, c0,1c, c0,1b, c0,1a, 00,16, 00,13, 00,10, 00,0d,
-c0,0d, c0,03, 00,0a, 00,63, 00,15, 00,12, 00,0f, 00,0c,
-00,62, 00,09, 00,65, 00,64, 00,14, 00,11, 00,0e, 00,0b,
-00,08, 00,06, 00,03, 00,ff"
-
-readonly -a TLS13_KEY_SHARES=(
- "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "a" "b" "c" "d" "e" "f"
-  "10" "11" "12" "13" "14" "15" "16"
-"-----BEGIN EC PARAMETERS-----
-BggqhkjOPQMBBw==
------END EC PARAMETERS-----
------BEGIN EC PRIVATE KEY-----
-MHcCAQEEIHEhQsBkqt1i15mG1wluq/zLqDmjqNQegtgxyNBfRbZSoAoGCCqGSM49
-AwEHoUQDQgAEJP3GoZyVYrabOauJMWUZJxM0PEbtjTxW7K8V+JMDhJa+UyRQm8Tf
-2LDnzCAiuwzF8m0KhcloHEoptD2WBUmJlQ==
------END EC PRIVATE KEY-----
-"
-"-----BEGIN EC PARAMETERS-----
-BgUrgQQAIg==
------END EC PARAMETERS-----
------BEGIN EC PRIVATE KEY-----
-MIGkAgEBBDA7MCUdHy2+Kc73fWph++jWo18LHzzm7SKLgycQBNtmeJu3w1y9pK0G
-EXgAWsIePIOgBwYFK4EEACKhZANiAAT/x7tN8plE6gbA6D4Igp3ash5EvZxvNqdG
-Q50fcDrIco91ybaVlg2tdngZgurTzte+jv7kdkYrILUmLnXxAUGg4d86yStfcZaI
-rDEB8Hc9BgJkFFoLSsXMVCKfoEo777k=
------END EC PRIVATE KEY-----
-"
-"-----BEGIN EC PARAMETERS-----
-BgUrgQQAIw==
------END EC PARAMETERS-----
------BEGIN EC PRIVATE KEY-----
-MIHbAgEBBEFjBqkejwKserOf+LoY6xeSUUoLSZQDz/oNLXLB3NQJ3ewDkhbjOvcL
-jG1on33V080fXRTN3eNdfvzcqDw4c0GGCKAHBgUrgQQAI6GBiQOBhgAEAHuBnMpQ
-+30lnd/gWrHwjLrXQ+EwtxYzMjSDkfRxr0UQ0YuzDNzsVP0azylC06BUlcAvVgiX
-+61BiUapw+37EORuAaHOlob0nobmFND7peN0YglQuBeSdqK3cbdP/u9jffGr2H99
-bONJgO7LSp05PXa79CEi8sydmKYiH1pSLAzRiQnh
------END EC PRIVATE KEY-----
-" "1a" "1b" "1c"
-"-----BEGIN PRIVATE KEY-----
-MC4CAQAwBQYDK2VuBCIEIACiKGKr1nm2eobXvsI3HrWNKR5wEVAIf7KaCmDPxsJR
------END PRIVATE KEY-----
-" "1e" "1f"
- "20" "21" "22" "23" "24" "25" "26" "27" "28" "29" "2a" "2b" "2c" "2d" "2e" "2f"
- "30" "31" "32" "33" "34" "35" "36" "37" "38" "39" "3a" "3b" "3c" "3d" "3e" "3f"
- "40" "41" "42" "43" "44" "45" "46" "47" "48" "49" "4a" "4b" "4c" "4d" "4e" "4f"
- "50" "51" "52" "53" "54" "55" "56" "57" "58" "59" "5a" "5b" "5c" "5d" "5e" "5f"
- "60" "61" "62" "63" "64" "65" "66" "67" "68" "69" "6a" "6b" "6c" "6d" "6e" "6f"
- "70" "71" "72" "73" "74" "75" "76" "77" "78" "79" "7a" "7b" "7c" "7d" "7e" "7f"
- "80" "81" "82" "83" "84" "85" "86" "87" "88" "89" "8a" "8b" "8c" "8d" "8e" "8f"
- "90" "91" "92" "93" "94" "95" "96" "97" "98" "99" "9a" "9b" "9c" "9d" "9e" "9f"
- "a0" "a1" "a2" "a3" "a4" "a5" "a6" "a7" "a8" "a9" "aa" "ab" "ac" "ad" "ae" "af"
- "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "ba" "bb" "bc" "bd" "be" "bf"
- "c0" "c1" "c2" "c3" "c4" "c5" "c6" "c7" "c8" "c9" "ca" "cb" "cc" "cd" "ce" "cf"
- "d0" "d1" "d2" "d3" "d4" "d5" "d6" "d7" "d8" "d9" "da" "db" "dc" "dd" "de" "df"
- "e0" "e1" "e2" "e3" "e4" "e5" "e6" "e7" "e8" "e9" "ea" "eb" "ec" "ed" "ee" "ef"
- "f0" "f1" "f2" "f3" "f4" "f5" "f6" "f7" "f8" "f9" "fa" "fb" "fc" "fd" "fe" "ff"
- "-----BEGIN PRIVATE KEY-----
-MIICJgIBADCCARcGCSqGSIb3DQEDATCCAQgCggEBAP//////////rfhUWKK7Spqv
-3FYgJz088di5xYPOLTaVqeE2QRRkM/vMk53OJJs++X0v42NjDHXY9oGyAq7EYXrT
-3x7V1f1lYSQz9R9fBm7QhWNlVT3tGvO1VxNef1fJNZhPDHDg5ot34qaJ2vPv6HId
-8VihNq3nNTCsyk9IOnl6vAqxgrMk+2HRCKlLssjj+7lq2rdg1/RoHU9Co945TfSu
-Vu3nY3K7GQsHp8juCm1wngL84c334uzANATNKDQvYZFy/pzphYP/jk8SMu7ygYPD
-/jsbTG+tczu1/LwuwiAFxY7xg30Wg7LG80omwbLv+ohrQjhhKFyX//////////8C
-AQIEggEEAoIBAHxYskjJGeKwSGdAf//JLxPmGRGP6Uylmt12QX5w1FfFXQVJdrsY
-unjdqhTwgV1vTZ1QApd0uZB//q8ZNNM8SZK0elY4ZJsHJAIdJ/ROmvPvkMCkU0fK
-S/uUHroP6tEDyKF+v7ooiBF2KXS5CkOYRTKhiOBaWGsdhiFIkd+O7oY6oyhPxPNT
-2zQEdhIu3ZgFG/ZcscdliMPMmZnKvt/dF4yV8RnCHl3MRDRdL/3McDAb4z89bWqR
-HRexppcgNa9lhOvR+nF/55NCzT3KwkFPQODQmMRH3bzmME+48HZrFcaaom3/DGt+
-EC+vidtEr4YW86tV6jvig5+uNR1mIKpE8N4=
------END PRIVATE KEY-----
-"
-"-----BEGIN PRIVATE KEY-----
-MIIDJgIBADCCAZcGCSqGSIb3DQEDATCCAYgCggGBAP//////////rfhUWKK7Spqv
-3FYgJz088di5xYPOLTaVqeE2QRRkM/vMk53OJJs++X0v42NjDHXY9oGyAq7EYXrT
-3x7V1f1lYSQz9R9fBm7QhWNlVT3tGvO1VxNef1fJNZhPDHDg5ot34qaJ2vPv6HId
-8VihNq3nNTCsyk9IOnl6vAqxgrMk+2HRCKlLssjj+7lq2rdg1/RoHU9Co945TfSu
-Vu3nY3K7GQsHp8juCm1wngL84c334uzANATNKDQvYZFy/pzphYP/jk8SMu7ygYPD
-/jsbTG+tczu1/LwuwiAFxY7xg30Wg7LG80omwbLv+ohrQjhhH8/c3jVbO2UZA1u8
-NPTe+ZwCOGG0b8nW5skHetkdJpH39+5ZjLD6wYbZHK7+EwmFE5JwtBMMk7xDeUT0
-/URS4tdN02Ty4h5x9Uv/XK6Cq5yd9p7obSvFIjY6DavFIZebDeraHb+aQtXESE4K
-vNBr+lPd7zwbIO4/1Z18JeQdK2bGLjf//////////wIBAgSCAYQCggGAV6hlUz0f
-RwpauhaumL+dFJQcZHgYghHX9JfNDZv1uMzkTiKxgVutrtFmfHoaTaYNgw+HEQSF
-ZRnGzyOXb14/ZoGWo727N4T5usOqINFcHIeAbPiRimo0mwS7ivYKxEFBaw4N7OyE
-zfNKAYWNQe0J+R2FLMKBSbJ+b1nGQ/cUSQDffDpKSUS94+XxwxcvNaCv9Ygtkvnl
-e/t61L/0eQu/nmi0o7PzR4brmyVTXGnj2LujG/KOtIB4pXQ1GqrvsYLB3pCUTDdA
-E0heXfpYGZJK10ByMkWmOuH3pCuI8C+7+Bh7JwQAXUtSpZ+hp1Bz7v1PKwY/3fG1
-2HcPXp85q5N9x9zYZv1vmwFAd0nTdoWdtMbiEJxhCdr6sRpi1+KPg6W3Kqtfcv2f
-ZZC6MwVFtxogjzIlXt68O7HRH7Adz+DGhEeZqdxIQpaQR50p4LF7gqQ/mzXq8oCe
-XKC3XxrfV5h3OrPEL/zNTd2pzh3LLQB349aOHNz1F+3YPyPlvwOsXkeT
------END PRIVATE KEY-----
-"
-"-----BEGIN PRIVATE KEY-----
-MIIEJgIBADCCAhcGCSqGSIb3DQEDATCCAggCggIBAP//////////rfhUWKK7Spqv
-3FYgJz088di5xYPOLTaVqeE2QRRkM/vMk53OJJs++X0v42NjDHXY9oGyAq7EYXrT
-3x7V1f1lYSQz9R9fBm7QhWNlVT3tGvO1VxNef1fJNZhPDHDg5ot34qaJ2vPv6HId
-8VihNq3nNTCsyk9IOnl6vAqxgrMk+2HRCKlLssjj+7lq2rdg1/RoHU9Co945TfSu
-Vu3nY3K7GQsHp8juCm1wngL84c334uzANATNKDQvYZFy/pzphYP/jk8SMu7ygYPD
-/jsbTG+tczu1/LwuwiAFxY7xg30Wg7LG80omwbLv+ohrQjhhH8/c3jVbO2UZA1u8
-NPTe+ZwCOGG0b8nW5skHetkdJpH39+5ZjLD6wYbZHK7+EwmFE5JwtBMMk7xDeUT0
-/URS4tdN02Ty4h5x9Uv/XK6Cq5yd9p7obSvFIjY6DavFIZebDeraHb+aQtXESE4K
-vNBr+lPd7zwbIO4/1Z18JeQdK2aeHvFub1LDFk30+3kw6eTliFe2rH1fQtafbRh3
-Y88dVQNABIf1W6V+Mcx6cTXIhu+0MYrtah4BLZ5oMqkHYAqRgTDEbcd4+XGtADgJ
-KZmjM8uLehoduT1xQAA8Kk7OqfmNCswKgpHNzsl9z47JtVp/iKRrTbWoUfRBguHG
-igB+XmVfav//////////AgECBIICBAKCAgBKs8VkNMjroMib7Wuw71hVoHiB7lF9
-3FQsDwU3y//RgETN2CEx8gdarvb35ldNEkypxtiaYck+a5qKVkP8uW4/AUoGlH4V
-mIVz8R9e0Cewc4X8229+AgvyguaEhJHozp7EqIYEYlpLyn5GL53l2OYvBB3eH9Yi
-yjYKe5vCe16Jy88oJYrS6+ybYLXHcfJsLHIppMS17KuDdH/DUiCvy5HE5fA5ufD3
-ExQImgsDa3rm8nW6NUCix9Pl4X5OkWieYE7pXBePZ8Yk8BD4JpPbhsh/9husS4XL
-/IpSq+tzgXq44SKQv0o9hbkGaxR6xmTjTwOjRiqW1D/1pS/wHxZbH1qbgJSKq7Fx
-6VZZjH5Hyx9Zh5p3mksa7iZ4DQXVW/8ffz+8UdVRQolVUQxXWihcU5qfdtmDEPI0
-4dRR5mI/Pk1n7lAhdyE4H/Tz0TmqItfScZvNaj6RbPbk6KOapgHFKIX7dmtPxAOv
-oMMudOwsBg7md3CY08zH/XdE6O8lmVgCJQMjfwJ7QMayOKL1NYNMmUDPP0WIxOyz
-5UJj3GzmNrKgYftgr2o8blEwwDbETYN/hpgTPyWl8ieVxK2bn7SX8dFXXEwSdCAt
-Cg5c3H+YOc+ahx7VYXJtBDyAKuygUKnVqZ1ht6/xLUyJUxiSMZLbFKHBLkR3UuQa
-HyRwI92yYN4+Zg==
------END PRIVATE KEY-----
-"
-"-----BEGIN PRIVATE KEY-----
-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
------END PRIVATE KEY-----
-"
-"-----BEGIN PRIVATE KEY-----
-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
-Y9zPgF69gpA9VpStqLAqHxBvEm4BYFoFyfw=
------END PRIVATE KEY-----
-" "105" "106" "107" "108" "109" "10a" "10b" "10c" "10d" "10e" "10f" )
-
 ###### Cipher suite information #####
 declare -i TLS_NR_CIPHERS=0
 declare TLS_CIPHER_HEXCODE=()
@@ -10551,7 +10322,7 @@ get_install_dir() {
           CIPHERS_BY_STRENGTH_FILE="$RUN_DIR/etc/cipher-mapping.txt"
           [[ -z "$TESTSSL_INSTALL_DIR" ]] && TESTSSL_INSTALL_DIR="$RUN_DIR"          # probably TESTSSL_INSTALL_DIR
      fi
-     
+
      [[ -r "$TESTSSL_INSTALL_DIR/etc/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/etc/cipher-mapping.txt"
      if [[ ! -r "$CIPHERS_BY_STRENGTH_FILE" ]]; then
           [[ -r "$RUN_DIR/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$RUN_DIR/cipher-mapping.txt"
@@ -10595,6 +10366,17 @@ get_install_dir() {
           ignore_no_or_lame "Type \"yes\" to ignore this warning and proceed at your own risk" "yes"
           [[ $? -ne 0 ]] && exit -2
      fi
+
+     TLS_DATA_FILE=$TESTSSL_INSTALL_DIR/etc/tls_data.txt
+     if [[ ! -r "$TLS_DATA_FILE" ]]; then
+          prln_warning "\nATTENTION: No TLS data file found -- needed for socket based handshakes"
+          outln "Please note from 2.9dev on $PROG_NAME needs files in \"\$TESTSSL_INSTALL_DIR/etc/\" to function correctly."
+          outln
+          ignore_no_or_lame "Type \"yes\" to ignore this warning and proceed at your own risk" "yes"
+          [[ $? -ne 0 ]] && exit -2
+     else
+          . $TLS_DATA_FILE
+     fi
 }
 
 
@@ -12505,80 +12287,84 @@ lets_roll() {
 
 ################# main #################
 
-ret=0
-ip=""
 
-lets_roll init
-initialize_globals
-parse_cmd_line "$@"
-# html_header() needs to be called early! Otherwiseif  html_out() is called before html_header() and the
-# command line contains --htmlfile  or --html, it'll make problems with html output, see #692.
-# json_header and csv_header can be called later but for context reasons we'll leave it here
-html_header
-json_header
-csv_header
-get_install_dir
-set_color_functions
-maketempf
-find_openssl_binary
-prepare_debug
-prepare_arrays
-mybanner
-check_proxy
-check4openssl_oldfarts
-check_bsd_mount
+#main() {
+#     local ret=0
+#     local ip=""
+     ret=0
+     ip=""
 
-if "$do_display_only"; then
-     prettyprint_local "$PATTERN2SHOW"
-     exit $?
-fi
+     lets_roll init
+     initialize_globals
+     parse_cmd_line "$@"
+     # html_header() needs to be called early! Otherwise if html_out() is called before html_header() and the
+     # command line contains --htmlfile  or --html, it'll make problems with html output, see #692.
+     # json_header and csv_header can be called later but for context reasons we'll leave it here
+     html_header
+     json_header
+     csv_header
+     get_install_dir
+     set_color_functions
+     maketempf
+     find_openssl_binary
+     prepare_debug
+     prepare_arrays
+     mybanner
+     check_proxy
+     check4openssl_oldfarts
+     check_bsd_mount
 
-fileout_banner
+     if "$do_display_only"; then
+          prettyprint_local "$PATTERN2SHOW"
+          exit $?
+     fi
+     fileout_banner
 
-if "$do_mass_testing"; then
+     if "$do_mass_testing"; then
+          prepare_logging
+          run_mass_testing
+          exit $?
+     fi
+     html_banner
+
+     #TODO: there shouldn't be the need for a special case for --mx, only the ip adresses we would need upfront and the do-parser
+     if "$do_mx_all_ips"; then
+          query_globals                                     # if we have just 1x "do_*" --> we do a standard run -- otherwise just the one specified
+          [[ $? -eq 1 ]] && set_scanning_defaults
+          run_mx_all_ips "${URI}" $PORT                     # we should reduce run_mx_all_ips to the stuff neccessary as ~15 lines later we have sililar code
+          exit $?
+     fi
+
+     [[ -z "$NODE" ]] && parse_hn_port "${URI}"             # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now
      prepare_logging
-     run_mass_testing
-     exit $?
-fi
-
-html_banner
-
-#TODO: there shouldn't be the need for a special case for --mx, only the ip adresses we would need upfront and the do-parser
-if $do_mx_all_ips; then
-     query_globals                                     # if we have just 1x "do_*" --> we do a standard run -- otherwise just the one specified
-     [[ $? -eq 1 ]] && set_scanning_defaults
-     run_mx_all_ips "${URI}" $PORT                     # we should reduce run_mx_all_ips to the stuff neccessary as ~15 lines later we have sililar code
-     exit $?
-fi
-
-[[ -z "$NODE" ]] && parse_hn_port "${URI}"                                 # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now
-prepare_logging
-if ! determine_ip_addresses; then
-     fatal "No IP address could be determined" 2
-fi
-if [[ -n "$CMDLINE_IP" ]]; then
-     #  we just test the one supplied
-     lets_roll "${STARTTLS_PROTOCOL}"
-     ret=$?
-else                                                                       # no --ip was supplied
-     if [[ $(count_words "$(echo -n "$IPADDRs")") -gt 1 ]]; then           # we have more than one ipv4 address to check
-          pr_bold "Testing all IPv4 addresses (port $PORT): "; outln "$IPADDRs"
-          for ip in $IPADDRs; do
-               draw_line "-" $((TERM_WIDTH * 2 / 3))
-               outln
-               NODEIP="$ip"
-               lets_roll "${STARTTLS_PROTOCOL}"
-               ret=$(($? + ret))
-          done
-          draw_line "-" $((TERM_WIDTH * 2 / 3))
-          outln
-          pr_bold "Done testing now all IP addresses (on port $PORT): "; outln "$IPADDRs"
-     else                                                                  # we need just one ip4v to check
-          NODEIP="$IPADDRs"
+     if ! determine_ip_addresses; then
+          fatal "No IP address could be determined" 2
+     fi
+     if [[ -n "$CMDLINE_IP" ]]; then
+          #  we just test the one supplied
           lets_roll "${STARTTLS_PROTOCOL}"
           ret=$?
+     else                                                                       # no --ip was supplied
+          if [[ $(count_words "$(echo -n "$IPADDRs")") -gt 1 ]]; then           # we have more than one ipv4 address to check
+               pr_bold "Testing all IPv4 addresses (port $PORT): "; outln "$IPADDRs"
+               for ip in $IPADDRs; do
+                    draw_line "-" $((TERM_WIDTH * 2 / 3))
+                    outln
+                    NODEIP="$ip"
+                    lets_roll "${STARTTLS_PROTOCOL}"
+                    ret=$(($? + ret))
+               done
+               draw_line "-" $((TERM_WIDTH * 2 / 3))
+               outln
+               pr_bold "Done testing now all IP addresses (on port $PORT): "; outln "$IPADDRs"
+          else                                                                  # we need just one ip4v to check
+               NODEIP="$IPADDRs"
+               lets_roll "${STARTTLS_PROTOCOL}"
+               ret=$?
+          fi
      fi
-fi
+#}
 
+#main
 exit $?
 

From 3351f8832c7c8dc742f5d668073d1692666773b3 Mon Sep 17 00:00:00 2001
From: Dirk 
Date: Thu, 6 Apr 2017 11:23:57 +0200
Subject: [PATCH 26/27] mute the error message using bash3, see #697 (2.9dev)

---
 testssl.sh | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/testssl.sh b/testssl.sh
index df47d6f..6ce1cd0 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -922,12 +922,13 @@ html_footer() {
 
 ###### START helper function definitions ######
 
-toupper() { echo -n "${1^^}" ;  }
-tolower() { echo -n "${1,,}" ;  }
-if ! toupper aaa 2>&1 >/dev/null; then
-     # Older bash can't do this (MacOS X), even SLES 11, see #697
+if [[ "$BASH_VERSINFO" == 3 ]]; then
+     # older bash can't do this (MacOS X), even SLES 11, see #697 and also
      toupper() { tr 'a-z' 'A-Z' <<< "$1"; }
      tolower() { tr 'A-Z' 'a-z' <<< "$1"; }
+else
+     toupper() { echo -n "${1^^}"; }
+     tolower() { echo -n "${1,,}"; }
 fi
 
 debugme() {

From e2f5d5c3cf8102892e6e9ddfbf22996df721530d Mon Sep 17 00:00:00 2001
From: Dirk 
Date: Thu, 6 Apr 2017 11:33:54 +0200
Subject: [PATCH 27/27] updated comments

---
 testssl.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/testssl.sh b/testssl.sh
index 6ce1cd0..9c2fc56 100755
--- a/testssl.sh
+++ b/testssl.sh
@@ -82,7 +82,7 @@ readonly PS4='|${LINENO}> \011${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
 # see stackoverflow.com/questions/5014823/how-to-profile-a-bash-shell-script-slow-startup#20855353
 # how to paste both in order to do performance analysis
 DEBUGTIME=${DEBUGTIME:-false}
-DEBUG_ALLINONE=${DEBUG_ALLINONE:-false}           # true: do debugging in one sceen (old behaviour for just debugging)
+DEBUG_ALLINONE=${DEBUG_ALLINONE:-false}           # true: do debugging in one sceen (old behaviour for testssl.sh and bash3's default
 if grep -q xtrace <<< "$SHELLOPTS"; then
      if "$DEBUGTIME"; then
           # separate debugging, doesn't mess up the screen, $DEBUGTIME determines whether we also do performance analysis
@@ -923,7 +923,7 @@ html_footer() {
 ###### START helper function definitions ######
 
 if [[ "$BASH_VERSINFO" == 3 ]]; then
-     # older bash can't do this (MacOS X), even SLES 11, see #697 and also
+     # older bash can do this only (MacOS X), even SLES 11, see #697
      toupper() { tr 'a-z' 'A-Z' <<< "$1"; }
      tolower() { tr 'A-Z' 'a-z' <<< "$1"; }
 else