#!/usr/bin/env bash
#
# vim:ts=5:sw=5:expandtab
# we have a spaces softtab, that ensures readability with other editors too

# testssl.sh is a program for spotting weak SSL/TLS encryption, ciphers, protocols and some
# vulnerabilities or features. It may or may be not distributed by your distribution.
# The upstream versions are available (please leave the links intact):
#
# Development version       https://github.com/drwetter/testssl.sh
# Stable version            https://testssl.sh
# File bugs at github       https://github.com/drwetter/testssl.sh/issues
#
# Project lead and initiator: Dirk Wetter, copyleft: 2007-today, contributions so far see CREDITS.md
# Main contributions from David Cooper
# Project lead and initiator: Dirk Wetter, copyleft: 2007-today.
# Main contributions from David Cooper. Further contributors see CREDITS.md .
#
# License: GPLv2, see https://opensource.org/licenses/gpl-2.0.php and
# ccompanying license "LICENSE.txt". Redistribution + modification under this
# license permitted.
# If you enclose this program or parts of it in your software, it has to be
# accompanied by the same license (see link). Do not violate the license.
# If you do not agree to these terms, do not use it in the first place!
#
# OpenSSL, which is being used and maybe distributed via one of this projects'
# web sites, is subject to their licensing: https://www.openssl.org/source/license.txt
#
# The client simulation data comes from SSLlabs and is licensed to the 'Qualys SSL Labs
# Terms of Use' (v2.2), see https://www.ssllabs.com/downloads/Qualys_SSL_Labs_Terms_of_Use.pdf,
# stating a CC BY 3.0 US license: https://creativecommons.org/licenses/by/3.0/us/
#
# Please note:  USAGE WITHOUT ANY WARRANTY, THE SOFTWARE IS PROVIDED "AS IS".
# USE IT AT your OWN RISK!
# Seriously! The threat is you run this code on your computer and untrusted input e.g.
# could be supplied from a server you are querying.
#
# HISTORY:
# Back in 2006 it all started with a few openssl commands...
# That's because openssl is a such a good swiss army knife (see e.g.
# https://wiki.openssl.org/index.php/Command_Line_Utilities) that it was difficult to resist
# wrapping some shell commands around it, which I used for my pen tests. This is how
# everything started.
# Now it has grown up, it has bash socket support for most features, which has been basically
# replacing more and more functions of OpenSSL and some sockets functions serve as some kind
# of central functions.
#
# WHY BASH?
# Cross-platform is one of the three main goals of this script. Second: Ease of installation.
# No compiling, install gems, go to CPAN, use pip etc. Third: Easy to use and to interpret
# the results.
# /bin/bash including the builtin sockets fulfill all that.  The socket checks in bash may sound
# cool and unique -- they are -- but probably you can achieve e.g. the same result with my favorite
# interactive shell: zsh (zmodload zsh/net/socket -- checkout zsh/net/tcp) too! Oh, and btw.
# ksh93 has socket support too.
# Also bash is quite powerful if you use it appropriately: It can operate on patterns, process lines
# and deal perfectly with regular expressions -- without external binaries.
# /bin/bash though is way more often used within Linux and it's perfect for cross platform support.
# MacOS X has it and also under Windows the MSYS2 extension or Cygwin as well as Bash on Windows (WSL)
# has /bin/bash.
#
# Q: So what's the difference to www.ssllabs.com/ssltest/ or sslcheck.globalsign.com/ ?
# A: As of now ssllabs only check 1) webservers 2) on standard ports, 3) reachable from the
#    internet. And those examples above 4) are 3rd parties. If these restrictions are all fine
#    with you and you need a management compatible rating -- go ahead and use those.
#
# But also if your fine with those restrictions: testssl.sh is meant as a tool in your hand
# and it's way more flexible.  Oh, and did I mention testssl.sh is open source?
#
#################### Stop talking, action now ####################


########### Definition of error codes
#
declare -r ERR_BASH=255            # Bash version incorrect
declare -r ERR_CMDLINE=254         # Cmd line couldn't be parsed
declare -r ERR_FCREATE=253         # Output file couldn't be created
declare -r ERR_FNAMEPARSE=252      # Input file couldn't be parsed
declare -r ERR_NOSUPPORT=251       # Feature requested is not supported
declare -r ERR_OSSLBIN=250         # Problem with OpenSSL binary
declare -r ERR_DNSBIN=249          # Problem with DNS lookup binaries
declare -r ERR_OTHERCLIENT=248     # Other client problem
declare -r ERR_DNSLOOKUP=247       # Problem with resolving IP addresses or names
declare -r ERR_CONNECT=246         # Connectivity problem
declare -r ERR_CLUELESS=245        # Weird state, either though user options or testssl.sh
declare -r ERR_RESOURCE=244        # Resources testssl.sh needs couldn't be read
declare -r ERR_CHILD=242           # Child received a signal from master
declare -r ALLOK=0                 # All is fine


[ -z "${BASH_VERSINFO[0]}" ] && printf "\n\033[1;35m Please make sure you're using \"bash\"! Bye...\033[m\n\n" >&2 && exit $ERR_BASH
[ $(kill -l | grep -c SIG) -eq 0 ] && printf "\n\033[1;35m Please make sure you're calling me without leading \"sh\"! Bye...\033[m\n\n"  >&2 && exit $ERR_BASH
[ ${BASH_VERSINFO[0]} -lt 3 ] && printf "\n\033[1;35m Minimum requirement is bash 3.2. You have $BASH_VERSION \033[m\n\n"  >&2 && exit $ERR_BASH
[ ${BASH_VERSINFO[0]} -le 3 ] && [ ${BASH_VERSINFO[1]} -le 1 ] && printf "\n\033[1;35m Minimum requirement is bash 3.2. You have $BASH_VERSION \033[m\n\n"  >&2 && exit $ERR_BASH

########### Debugging helpers + profiling
#
declare -r PS4='|${LINENO}> \011${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
DEBUGTIME=${DEBUGTIME:-false}                     # https://stackoverflow.com/questions/5014823/how-to-profile-a-bash-shell-script-slow-startup#20855353
DEBUG_ALLINONE=${DEBUG_ALLINONE:-false}           # true: do debugging in one screen (old behavior for testssl.sh and bash3's default
                                                  # false: needed for performance analysis or useful for just having an extra file
DEBUG_ALLINONE=${SETX:-false}                     # SETX as a shortcut for old style debugging, overriding DEBUG_ALLINONE
if [[ "$SHELLOPTS" =~ xtrace ]]; 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
     else
          if ! "$DEBUG_ALLINONE"; then
               exec 42>| /tmp/testssl-$$.log
               BASH_XTRACEFD=42
          fi
     fi
fi

########### Traps! Make sure that temporary files are cleaned up after use in ANY case
#
trap "cleanup" EXIT
trap "sig_cleanup" INT QUIT TERM
trap "child_error" USR1


########### Internal definitions
#
declare -r VERSION="3.1dev"
declare -r SWCONTACT="dirk aet testssl dot sh"
grep -E -q "dev|rc|beta" <<< "$VERSION" && \
     SWURL="https://testssl.sh/dev/" ||
     SWURL="https://testssl.sh/"
if git log &>/dev/null; then
     declare -r GIT_REL="$(git log --format='%h %ci' -1 2>/dev/null | awk '{ print $1" "$2" "$3 }')"
     declare -r GIT_REL_SHORT="$(git log --format='%h %ci' -1 2>/dev/null | awk '{ print $1 }')"
     declare -r REL_DATE="$(git log --format='%h %ci' -1 2>/dev/null | awk '{ print $2 }')"
fi
declare -r PROG_NAME="$(basename "$0")"
declare -r RUN_DIR="$(dirname "$0")"
declare -r SYSTEM="$(uname -s)"
declare -r SYSTEMREV="$(uname -r)"
HNAME="$(hostname)"
HNAME="${HNAME%%.*}"
declare CMDLINE
CMDLINE_PARSED=""                                 # This makes sure we don't let early fatal() write into files when files aren't created yet
declare -r -a CMDLINE_ARRAY=("$@")                # When performing mass testing, the child processes need to be sent the
declare -a MASS_TESTING_CMDLINE                   # command line in the form of an array (see #702 and https://mywiki.wooledge.org/BashFAQ/050).
declare -a SKIP_TESTS=()                          # This array hold the checks to be skipped


########### Defining (and presetting) variables which can be changed
#
# Following variables make use of $ENV and can also be used like "<VAR>=<value> ./testssl.sh <URI>"
declare -x OPENSSL
OPENSSL_TIMEOUT=${OPENSSL_TIMEOUT:-""}  # Default connect timeout with openssl before we call the server side unreachable
CONNECT_TIMEOUT=${CONNECT_TIMEOUT:-""}  # Default connect timeout with sockets before we call the server side unreachable
PHONE_OUT=${PHONE_OUT:-false}           # Whether testssl can retrieve CRLs and OCSP
FAST_SOCKET=${FAST_SOCKET:-false}       # EXPERIMENTAL feature to accelerate sockets -- DO NOT USE it for production
COLOR=${COLOR:-2}                       # 3: Extra color (ciphers, curves), 2: Full color, 1: B/W only 0: No ESC at all
COLORBLIND=${COLORBLIND:-false}         # if true, swap blue and green in the output
SHOW_EACH_C=${SHOW_EACH_C:-false}       # where individual ciphers are tested show just the positively ones tested
SHOW_SIGALGO=${SHOW_SIGALGO:-false}     # "secret" switch whether testssl.sh shows the signature algorithm for -E / -e
SNEAKY=${SNEAKY:-false}                 # is the referer and useragent we leave behind just usual?
QUIET=${QUIET:-false}                   # don't output the banner. By doing this you acknowledge usage term appearing in the banner
SSL_NATIVE=${SSL_NATIVE:-false}         # we do per default bash sockets where possible "true": switch back to "openssl native"
ASSUME_HTTP=${ASSUME_HTTP:-false}       # in seldom cases (WAF, old servers, grumpy SSL) service detection fails. "True" enforces HTTP checks
BASICAUTH=${BASICAUTH:-""}              # HTTP basic auth credentials can be set here like user:pass
BUGS=${BUGS:-""}                        # -bugs option from openssl, needed for some BIG IP F5
WARNINGS=${WARNINGS:-""}                # can be either off or batch
DEBUG=${DEBUG:-0}                       # 1: normal output the files in /tmp/ are kept for further debugging purposes
                                        # 2: list more what's going on , also lists some errors of connections
                                        # 3: slight hexdumps + other info,
                                        # 4: display bytes sent via sockets
                                        # 5: display bytes received via sockets
                                        # 6: whole 9 yards
FAST=${FAST:-false}                     # preference: show only first cipher, run_allciphers with openssl instead of sockets
WIDE=${WIDE:-false}                     # whether to display for some options just ciphers or a table w hexcode/KX,Enc,strength etc.
MASS_TESTING_MODE=${MASS_TESTING_MODE:-serial}    # can be serial or parallel. Subject to change
LOGFILE="${LOGFILE:-""}"                # logfile if used
JSONFILE="${JSONFILE:-""}"              # jsonfile if used
CSVFILE="${CSVFILE:-""}"                # csvfile if used
HTMLFILE="${HTMLFILE:-""}"              # HTML if used
FNAME=${FNAME:-""}                      # file name to read commands from
FNAME_PREFIX=${FNAME_PREFIX:-""}        # output filename prefix, see --outprefix
APPEND=${APPEND:-false}                 # append to csv/json file instead of overwriting it
[[ -z "$NODNS" ]] && declare NODNS      # If unset it does all DNS lookups per default. "min" only for hosts or "none" at all
HAS_IPv6=${HAS_IPv6:-false}             # if you have OpenSSL with IPv6 support AND IPv6 networking set it to yes
ALL_CLIENTS=${ALL_CLIENTS:-false}       # do you want to run all client simulation form all clients supplied by SSLlabs?
OFFENSIVE=${OFFENSIVE:-true}            # do you want to include offensive vulnerability tests which may cause blocking by an IDS?
ADDTL_CA_FILES="${ADDTL_CA_FILES:-""}"  # single file with a CA in PEM format or comma separated lists of them

########### Tuning vars which cannot be set by a cmd line switch. Use instead e.g "HEADER_MAXSLEEP=10 ./testssl.sh <your_args_here>"
#
TESTSSL_INSTALL_DIR="${TESTSSL_INSTALL_DIR:-""}"  # If you run testssl.sh and it doesn't find it necessary file automagically set TESTSSL_INSTALL_DIR
CA_BUNDLES_PATH="${CA_BUNDLES_PATH:-""}"          # You can have your CA stores some place else
EXPERIMENTAL=${EXPERIMENTAL:-false}     # a development hook which allows us to disable code
PROXY_WAIT=${PROXY_WAIT:-20}            # waiting at max 20 seconds for socket reply through proxy
DNS_VIA_PROXY=${DNS_VIA_PROXY:-true}    # do DNS lookups via proxy. --ip=proxy reverses this
IGN_OCSP_PROXY=${IGN_OCSP_PROXY:-false} # Also when --proxy is supplied it is ignored when testing for revocation via OCSP via --phone-out
HEADER_MAXSLEEP=${HEADER_MAXSLEEP:-5}   # we wait this long before killing the process to retrieve a service banner / http header
MAX_SOCKET_FAIL=${MAX_SOCKET_FAIL:-2}   # If this many failures for TCP socket connects are reached we terminate
MAX_OSSL_FAIL=${MAX_OSSL_FAIL:-2}       # If this many failures for s_client connects are reached we terminate
MAX_HEADER_FAIL=${MAX_HEADER_FAIL:-2}   # If this many failures for HTTP GET are encountered we don't try again to get the header
MAX_WAITSOCK=${MAX_WAITSOCK:-10}        # waiting at max 10 seconds for socket reply. There shouldn't be any reason to change this.
CCS_MAX_WAITSOCK=${CCS_MAX_WAITSOCK:-5} # for the two CCS payload (each). There shouldn't be any reason to change this.
HEARTBLEED_MAX_WAITSOCK=${HEARTBLEED_MAX_WAITSOCK:-8}      # for the heartbleed payload. There shouldn't be any reason to change this.
STARTTLS_SLEEP=${STARTTLS_SLEEP:-10}    # max time wait on a socket for STARTTLS. MySQL has a fixed value of 1 which can't be overwritten (#914)
FAST_STARTTLS=${FAST_STARTTLS:-true}    # at the cost of reliability decrease the handshakes for STARTTLS
USLEEP_SND=${USLEEP_SND:-0.1}           # sleep time for general socket send
USLEEP_REC=${USLEEP_REC:-0.2}           # sleep time for general socket receive
HSTS_MIN=${HSTS_MIN:-179}               # >179 days is ok for HSTS
     HSTS_MIN=$((HSTS_MIN * 86400))     # correct to seconds
HPKP_MIN=${HPKP_MIN:-30}                # >=30 days should be ok for HPKP_MIN, practical hints?
     HPKP_MIN=$((HPKP_MIN * 86400))     # correct to seconds
DAYS2WARN1=${DAYS2WARN1:-60}            # days to warn before cert expires, threshold 1
DAYS2WARN2=${DAYS2WARN2:-30}            # days to warn before cert expires, threshold 2
VULN_THRESHLD=${VULN_THRESHLD:-1}       # if vulnerabilities to check >$VULN_THRESHLD we DON'T show a separate header line in the output each vuln. check
UNBRACKTD_IPV6=${UNBRACKTD_IPV6:-false} # some versions of OpenSSL (like Gentoo) don't support [bracketed] IPv6 addresses
NO_ENGINE=${NO_ENGINE:-false}           # if there are problems finding the (external) openssl engine set this to true
declare -r CLIENT_MIN_FS=5              # number of ciphers needed to run a test for FS
CAPATH="${CAPATH:-/etc/ssl/certs/}"     # Does nothing yet (FC has only a CA bundle per default, ==> openssl version -d)
GOOD_CA_BUNDLE=""                       # A bundle of CA certificates that can be used to validate the server's certificate
CERTIFICATE_LIST_ORDERING_PROBLEM=false # Set to true if server sends a certificate list that contains a certificate
                                        # that does not certify the one immediately preceding it. (See RFC 8446, Section 4.4.2)
STAPLED_OCSP_RESPONSE=""
HAS_DNS_SANS=false                      # Whether the certificate includes a subjectAltName extension with a DNS name or an application-specific identifier type.
MEASURE_TIME_FILE=${MEASURE_TIME_FILE:-""}
if [[ -n "$MEASURE_TIME_FILE" ]] && [[ -z "$MEASURE_TIME" ]]; then
     MEASURE_TIME=true
else
     MEASURE_TIME=${MEASURE_TIME:-false}
fi
DISPLAY_CIPHERNAMES="openssl"           # display OpenSSL ciphername (but both OpenSSL and RFC ciphernames in wide mode)
declare -r UA_STD="TLS tester from $SWURL"
declare -r UA_SNEAKY="Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0"

########### Initialization part, further global vars just being declared here
#
SYSTEM2=""                              # currently only being used for WSL = bash on windows
PRINTF=""                               # which external printf to use. Empty presets the internal one, see #1130
CIPHERS_BY_STRENGTH_FILE=""
TLS_DATA_FILE=""                        # mandatory file for socket-based handshakes
OPENSSL_LOCATION=""
IKNOW_FNAME=false
FIRST_FINDING=true                      # is this the first finding we are outputting to file?
JSONHEADER=true                         # include JSON headers and footers in HTML file, if one is being created
CSVHEADER=true                          # same for CSV
HTMLHEADER=true                         # same for HTML
SECTION_FOOTER_NEEDED=false             # kludge for tracking whether we need to close the JSON section object
GIVE_HINTS=false                        # give an additional info to findings
SERVER_SIZE_LIMIT_BUG=false             # Some servers have either a ClientHello total size limit or a 128 cipher limit (e.g. old ASAs)
MULTIPLE_CHECKS=false                   # need to know whether an MX record or a hostname resolves to multiple IPs to check
CHILD_MASS_TESTING=${CHILD_MASS_TESTING:-false}
TIMEOUT_CMD=""
HAD_SLEPT=0
NR_SOCKET_FAIL=0                        # Counter for socket failures
NR_OSSL_FAIL=0                          # .. for OpenSSL connects
NR_HEADER_FAIL=0                        # .. for HTTP_GET
PROTOS_OFFERED=""                       # This keeps which protocol is being offered. See has_server_protocol().
TLS12_CIPHER_OFFERED=""                 # This contains the hexcode of a cipher known to be supported by the server with TLS 1.2
CURVES_OFFERED=""                       # This keeps which curves have been detected. Just for error handling
KNOWN_OSSL_PROB=false                   # We need OpenSSL a few times. This variable is an indicator if we can't connect. Eases handling
DETECTED_TLS_VERSION=""                 # .. as hex string, e.g. 0300 or 0303
APP_TRAF_KEY_INFO=""                    # Information about the application traffic keys for a TLS 1.3 connection.
TLS13_ONLY=false                        # Does the server support TLS 1.3 ONLY?
OSSL_SHORTCUT=${OSSL_SHORTCUT:-false}   # Hack: if during the scan turns out the OpenSSL binary suports TLS 1.3 would be a better choice, this enables it.
TLS_EXTENSIONS=""
V2_HELLO_CIPHERSPEC_LENGTH=0
declare -r NPN_PROTOs="spdy/4a2,spdy/3,spdy/3.1,spdy/2,spdy/1,http/1.1"
# alpn_protos needs to be space-separated, not comma-seperated, including odd ones observed @ facebook and others, old ones like h2-17 omitted as they could not be found
declare -r ALPN_PROTOs="h2 spdy/3.1 http/1.1 grpc-exp h2-fb spdy/1 spdy/2 spdy/3 stun.turn stun.nat-discovery webrtc c-webrtc ftp"
declare -a SESS_RESUMPTION
TEMPDIR=""
TMPFILE=""
ERRFILE=""
CLIENT_AUTH=false
TLS_TICKETS=false
NO_SSL_SESSIONID=false
CERT_COMPRESSION=${CERT_COMPRESSION:-false}  # secret flag to set in addition to --devel for certificate compression
HOSTCERT=""                                  # File with host certificate, without intermediate certificate
HEADERFILE=""
HEADERVALUE=""
HTTP_STATUS_CODE=""
DH_GROUP_OFFERED=""
DH_GROUP_LEN_P=0
KEY_SHARE_EXTN_NR="33"                  # The extension number for key_share was changed from 40 to 51 in TLSv1.3 draft 23.
                                        # In order to support draft 23 and later in addition to earlier drafts, need to
                                        # know which extension number to use. Note that it appears that a single
                                        # ClientHello cannot advertise both draft 23 and later and earlier drafts.
                                        # Preset may help to deal with STARTTLS + TLS 1.3 draft 23 and later but not earlier.
BAD_SERVER_HELLO_CIPHER=false           # reserved for cases where a ServerHello doesn't contain a cipher offered in the ClientHello
GOST_STATUS_PROBLEM=false
PATTERN2SHOW=""
SOCK_REPLY_FILE=""
NW_STR=""
LEN_STR=""
SNI=""
POODLE=""                               # keep vulnerability status for TLS_FALLBACK_SCSV
OSSL_NAME=""                            # openssl name, in case of LibreSSL it's LibreSSL
OSSL_VER=""                             # openssl version, will be auto-determined
OSSL_VER_MAJOR=0
OSSL_VER_MINOR=0
OSSL_VER_APPENDIX="none"
CLIENT_PROB_NO=1
HAS_DH_BITS=${HAS_DH_BITS:-false}       # initialize openssl variables
HAS_CURVES=false
OSSL_SUPPORTED_CURVES=""
HAS_SSL2=false
HAS_SSL3=false
HAS_TLS13=false
HAS_X448=false
HAS_X25519=false
HAS_SIGALGS=false
HAS_PKUTIL=false
HAS_PKEY=false
HAS_NO_SSL2=false
HAS_NOSERVERNAME=false
HAS_CIPHERSUITES=false
HAS_SECLEVEL=false
HAS_COMP=false
HAS_NO_COMP=false
HAS_ALPN=false
HAS_NPN=false
HAS_FALLBACK_SCSV=false
HAS_PROXY=false
HAS_XMPP=false
HAS_XMPP_SERVER=false
HAS_POSTGRES=false
HAS_MYSQL=false
HAS_LMTP=false
HAS_NNTP=false
HAS_IRC=false
HAS_CHACHA20=false
HAS_AES128_GCM=false
HAS_AES256_GCM=false
HAS_ZLIB=false
HAS_DIG=false
HAS_HOST=false
HAS_DRILL=false
HAS_NSLOOKUP=false
HAS_IDN=false
HAS_IDN2=false
HAS_AVAHIRESOLVE=false
HAS_DIG_NOIDNOUT=false

OSSL_CIPHERS_S=""
PORT=443                                # unless otherwise auto-determined, see below
NODE=""
NODEIP=""
rDNS=""
CORRECT_SPACES=""                       # Used for IPv6 and proper output formatting
IPADDRs=""
IP46ADDRs=""
LOCAL_A=false                           # Does the $NODEIP come from /etc/hosts?
LOCAL_AAAA=false                        # Does the IPv6 IP come from /etc/hosts?
XMPP_HOST=""
PROXYIP=""                              # $PROXYIP:$PROXPORT is your proxy if --proxy is defined ...
PROXYPORT=""                            # ... and openssl has proxy support
PROXY=""                                # Once check_proxy() executed it contains $PROXYIP:$PROXPORT
VULN_COUNT=0
SERVICE=""                              # Is the server running an HTTP server, SMTP, POP or IMAP?
URI=""
CERT_FINGERPRINT_SHA2=""
RSA_CERT_FINGERPRINT_SHA2=""
STARTTLS_PROTOCOL=""
OPTIMAL_PROTO=""                        # Need this for IIS6 (sigh) + OpenSSL 1.0.2, otherwise some handshakes will fail see
                                        # https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892
STARTTLS_OPTIMAL_PROTO=""               # Same for STARTTLS, see https://github.com/drwetter/testssl.sh/issues/188
OPTIMAL_SOCKETS_PROTO=""                # Same for tls_sockets(). -- not yet used
ALL_FAILED_SOCKETS=true                 # Set to true if all attempts to connect to server using tls_sockets/sslv2_sockets failed
TLS_TIME=""                             # To keep the value of TLS server timestamp
TLS_NOW=""                              # Similar
TLS_DIFFTIME_SET=false                  # Tells TLS functions to measure the TLS difftime or not
NOW_TIME=""
HTTP_TIME=""
GET_REQ11=""
START_TIME=0                            # time in epoch when the action started
END_TIME=0                              # .. ended
SCAN_TIME=0                             # diff of both: total scan time
LAST_TIME=0                             # only used for performance measurements (MEASURE_TIME=true)
SERVER_COUNTER=0                        # Counter for multiple servers

TLS_LOW_BYTE=""                         # For "secret" development stuff, see -q below
HEX_CIPHER=""                           # "

GRADE_CAP=""                            # Keeps track of the current grading cap
GRADE_CAP_REASONS=()                    # Keeps track of all the reasons why grades are capped
GRADE_WARNINGS=()                       # Keeps track of all the grade warnings
KEY_EXCH_SCORE=100                      # Keeps track of the score for category 2 "Key Exchange Strength"
CIPH_STR_BEST=0                         # Keeps track of the best bit size for category 3 "Cipher Strength"
CIPH_STR_WORST=100000                   # Keeps track of the worst bit size for category 3 "Cipher Strength"
                                        # Intentionally set very high, so it can be set to 0, if necessary

########### Global variables for parallel mass testing
#
declare -r PARALLEL_SLEEP=1               # Time to sleep after starting each test
MAX_WAIT_TEST=${MAX_WAIT_TEST:-1200}      # Maximum time (in seconds) to wait for a test to complete
MAX_PARALLEL=${MAX_PARALLEL:-20}          # Maximum number of tests to run in parallel
                                          # This value may be made larger on systems with faster processors
declare -a -i PARALLEL_TESTING_PID=()     # process id for each child test (or 0 to indicate test has already completed)
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
declare FIRST_JSON_OUTPUT=true            # true if no output has been added to $JSONFILE yet.


########### Cipher suite information
#
declare -i TLS_NR_CIPHERS=0
declare TLS_CIPHER_HEXCODE=()
declare TLS_CIPHER_OSSL_NAME=()
declare TLS_CIPHER_RFC_NAME=()
declare TLS_CIPHER_SSLVERS=()
declare TLS_CIPHER_KX=()
declare TLS_CIPHER_AUTH=()
declare TLS_CIPHER_ENC=()
declare TLS_CIPHER_EXPORT=()
declare TLS_CIPHER_OSSL_SUPPORTED=()
declare TLS13_OSSL_CIPHERS="TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256:TLS_AES_128_CCM_8_SHA256"


########### Some predefinitions: date, sed (we always use tests for binaries and NOT try to determine
#   capabilities by querying the OS)
#
HAS_GNUDATE=false
HAS_FREEBSDDATE=false
HAS_OPENBSDDATE=false
if date -d @735275209 >/dev/null 2>&1; then
     if date -r @735275209  >/dev/null 2>&1; then
          # It can't do any conversion from a plain date output.
          HAS_OPENBSDDATE=true
     else
          HAS_GNUDATE=true
     fi
fi
# FreeBSD and OS X date(1) accept "-f inputformat", so do newer OpenBSD versions >~ 6.6.
date -j -f '%s' 1234567 >/dev/null 2>&1 && \
     HAS_FREEBSDDATE=true

echo A | sed -E 's/A//' >/dev/null 2>&1 && \
     declare -r HAS_SED_E=true || \
     declare -r HAS_SED_E=false

########### Terminal defintions
tty -s && \
     declare -r INTERACTIVE=true || \
     declare -r INTERACTIVE=false

if [[ -z $TERM_WIDTH ]]; then                               # No batch file and no otherwise predefined TERM_WIDTH
     if ! tput cols &>/dev/null || ! "$INTERACTIVE";then    # Prevent tput errors if running non interactive
          export TERM_WIDTH=${COLUMNS:-80}
     else
          export TERM_WIDTH=${COLUMNS:-$(tput cols)}        # For custom line wrapping and dashes
     fi
fi
TERM_CURRPOS=0                                              # Custom line wrapping needs alter the current horizontal cursor pos


########### Severity functions and globals
#
INFO=0
OK=0
LOW=1
MEDIUM=2
HIGH=3
CRITICAL=4
SEVERITY_LEVEL=0

set_severity_level() {
     local severity=$1

     if [[ "$severity" == LOW ]]; then
          SEVERITY_LEVEL=$LOW
     elif [[ "$severity" == MEDIUM ]]; then
          SEVERITY_LEVEL=$MEDIUM
     elif [[ "$severity" == HIGH ]]; then
          SEVERITY_LEVEL=$HIGH
     elif [[ "$severity" == CRITICAL ]]; then
          SEVERITY_LEVEL=$CRITICAL
     else
          # WARN and FATAL will always be logged as the represent scanning problems
          echo "Supported severity levels are LOW, MEDIUM, HIGH, CRITICAL!"
          help 1
     fi
}

show_finding() {
     local severity=$1

     ( [[ "$severity" == DEBUG ]] ) ||
     ( [[ "$severity" == INFO ]] && [[ $SEVERITY_LEVEL -le $INFO ]] ) ||
     ( [[ "$severity" == OK ]] && [[ $SEVERITY_LEVEL -le $OK ]] ) ||
     ( [[ "$severity" == LOW ]] && [[ $SEVERITY_LEVEL -le $LOW ]] ) ||
     ( [[ "$severity" == MEDIUM ]] && [[ $SEVERITY_LEVEL -le $MEDIUM ]] ) ||
     ( [[ "$severity" == HIGH ]] && [[ $SEVERITY_LEVEL -le $HIGH ]] ) ||
     ( [[ "$severity" == CRITICAL ]] && [[ $SEVERITY_LEVEL -le $CRITICAL ]] ) ||
     ( [[ "$severity" == WARN ]] ) ||
     ( [[ "$severity" == FATAL ]] )
}

########### Output functions

# For HTML output, replace any HTML reserved characters with the entity name
html_reserved(){
     local output
     "$do_html" || return 0
     #sed  -e 's/\&/\&amp;/g' -e 's/</\&lt;/g' -e 's/>/\&gt;/g' -e 's/"/\&quot;/g' -e "s/'/\&apos;/g" <<< "$1"
     output="${1//&/&amp;}"
     output="${output//</&lt;}"
     output="${output//>/&gt;}"
     output="${output//\"/&quot;}"
     output="${output//\'/&apos;}"
     printf -- "%s" "$output"
     return 0
}

html_out() {
     "$do_html" || return 0
     [[ -n "$HTMLFILE" ]] && [[ ! -d "$HTMLFILE" ]] && printf -- "%b" "$1" >> "$HTMLFILE"
}

# This is intentionally the same.
safe_echo()  { printf -- "%b" "$1"; }
tm_out()     { printf -- "%b" "$1"; }
tmln_out()   { printf -- "%b" "$1\n"; }

out()   { printf -- "%b" "$1"; html_out "$(html_reserved "$1")"; }
outln() { printf -- "%b" "$1\n"; html_out "$(html_reserved "$1")\n"; }

#TODO: Still no shell injection safe but if just run it from the cmd line: that's fine

# Color print functions, see also https://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html
tm_liteblue()   { [[ "$COLOR" -ge 2 ]] && ( "$COLORBLIND" && tm_out "\033[0;32m$1" || tm_out "\033[0;34m$1" ) || tm_out "$1"; tm_off; }    # not yet used
pr_liteblue()   { tm_liteblue "$1"; [[ "$COLOR" -ge 2 ]] && ( "$COLORBLIND" && html_out "<span style=\"color:#00cd00;\">$(html_reserved "$1")</span>" || html_out "<span style=\"color:#0000ee;\">$(html_reserved "$1")</span>" ) || html_out "$(html_reserved "$1")"; }
tmln_liteblue() { tm_liteblue "$1"; tmln_out; }
prln_liteblue() { pr_liteblue "$1"; outln; }

tm_blue()       { [[ "$COLOR" -ge 2 ]] && ( "$COLORBLIND" && tm_out "\033[1;32m$1" || tm_out "\033[1;34m$1" ) || tm_out "$1"; tm_off; }    # used for head lines of single tests
pr_blue()       { tm_blue "$1"; [[ "$COLOR" -ge 2 ]] && ( "$COLORBLIND" && html_out "<span style=\"color:lime;font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "<span style=\"color:#5c5cff;font-weight:bold;\">$(html_reserved "$1")</span>" ) || html_out "$(html_reserved "$1")"; }
tmln_blue()     { tm_blue "$1"; tmln_out; }
prln_blue()     { pr_blue "$1"; outln; }

# we should be able to use aliases here
tm_warning()    { [[ "$COLOR" -ge 2 ]] && tm_out "\033[0;35m$1" || tm_underline "$1"; tm_off; }                   # some local problem: one test cannot be done
tmln_warning()  { tm_warning "$1"; tmln_out; }                                                                    # litemagenta
pr_warning()    { tm_warning "$1"; [[ "$COLOR" -ge 2 ]] && html_out "<span style=\"color:#cd00cd;\">$(html_reserved "$1")</span>" || ( [[ "$COLOR" -eq 1 ]] && html_out "<u>$(html_reserved "$1")</u>" || html_out "$(html_reserved "$1")" ); }
prln_warning()  { pr_warning "$1"; outln; }

tm_magenta()    { [[ "$COLOR" -ge 2 ]] && tm_out "\033[1;35m$1" || tm_underline "$1"; tm_off; }                   # fatal error: quitting because of this!
tmln_magenta()  { tm_magenta "$1"; tmln_out; }
# different as warning above?
pr_magenta()    { tm_magenta "$1"; [[ "$COLOR" -ge 2 ]] && html_out "<span style=\"color:magenta;font-weight:bold;\">$(html_reserved "$1")</span>" || ( [[ "$COLOR" -eq 1 ]] && html_out "<u>$(html_reserved "$1")</u>" || html_out "$(html_reserved "$1")" ); }
prln_magenta()  { pr_magenta "$1"; outln; }

tm_litecyan()   { [[ "$COLOR" -ge 2 ]] && tm_out "\033[0;36m$1" || tm_out "$1"; tm_off; }                         # not yet used
tmln_litecyan() { tm_litecyan "$1"; tmln_out; }
pr_litecyan()   { tm_litecyan "$1"; [[ "$COLOR" -ge 2 ]] && html_out "<span style=\"color:#00cdcd;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; }
prln_litecyan() { pr_litecyan "$1"; outln; }

tm_cyan()       { [[ "$COLOR" -ge 2 ]] && tm_out "\033[1;36m$1" || tm_out "$1"; tm_off; }                         # additional hint
tmln_cyan()     { tm_cyan "$1"; tmln_out; }
pr_cyan()       { tm_cyan "$1"; [[ "$COLOR" -ge 2 ]] && html_out "<span style=\"color:cyan;font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; }
prln_cyan()     { pr_cyan "$1"; outln; }

tm_litegrey()   { [[ "$COLOR" -ne 0 ]] && tm_out "\033[0;37m$1" || tm_out "$1"; tm_off; }                         # ... https://github.com/drwetter/testssl.sh/pull/600#issuecomment-276129876
tmln_litegrey() { tm_litegrey "$1"; tmln_out; }                                                                   # not really usable on a black background, see ..
prln_litegrey() { pr_litegrey "$1"; outln; }
pr_litegrey()   { tm_litegrey "$1"; [[ "$COLOR" -ne 0 ]] && html_out "<span style=\"color:darkgray;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; }

tm_grey()       { [[ "$COLOR" -ne 0 ]] && tm_out "\033[1;30m$1" || tm_out "$1"; tm_off; }
pr_grey()       { tm_grey "$1"; [[ "$COLOR" -ne 0 ]] && html_out "<span style=\"color:#7f7f7f;font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; }
tmln_grey()     { tm_grey "$1"; tmln_out; }
prln_grey()     { pr_grey "$1"; outln; }

tm_svrty_good()   { [[ "$COLOR" -ge 2 ]] && ( "$COLORBLIND" && tm_out "\033[0;34m$1" || tm_out "\033[0;32m$1" ) || tm_out "$1"; tm_off; }   # litegreen (liteblue), This is good
tmln_svrty_good() { tm_svrty_good "$1"; tmln_out; }
pr_svrty_good()   { tm_svrty_good "$1"; [[ "$COLOR" -ge 2 ]] && ( "$COLORBLIND" && html_out "<span style=\"color:#0000ee;\">$(html_reserved "$1")</span>" || html_out "<span style=\"color:#00cd00;\">$(html_reserved "$1")</span>" ) || html_out "$(html_reserved "$1")"; }
prln_svrty_good() { pr_svrty_good "$1"; outln; }

tm_svrty_best()   { [[ "$COLOR" -ge 2 ]] && ( "$COLORBLIND" && tm_out "\033[1;34m$1" || tm_out "\033[1;32m$1" ) ||  tm_out "$1"; tm_off; }  # green (blue), This is the best
tmln_svrty_best() { tm_svrty_best "$1"; tmln_out; }
pr_svrty_best()   { tm_svrty_best "$1"; [[ "$COLOR" -ge 2 ]] && ( "$COLORBLIND" && html_out "<span style=\"color:#5c5cff;font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "<span style=\"color:lime;font-weight:bold;\">$(html_reserved "$1")</span>" ) || html_out "$(html_reserved "$1")"; }
prln_svrty_best() { pr_svrty_best "$1"; outln; }

tm_svrty_low()     { [[ "$COLOR" -ge 2 ]] && tm_out "\033[1;33m$1" || tm_out "$1"; tm_off; }         # yellow brown | academic or minor problem
tmln_svrty_low()   { tm_svrty_low "$1"; tmln_out; }
pr_svrty_low()     { tm_svrty_low "$1"; [[ "$COLOR" -ge 2 ]] && html_out "<span style=\"color:#cdcd00;font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; }
prln_svrty_low()   { pr_svrty_low "$1"; outln; }

tm_svrty_medium()  { [[ "$COLOR" -ge 2 ]] && tm_out "\033[0;33m$1" || tm_out "$1"; tm_off; }         # brown | it is not a bad problem but you shouldn't do this
pr_svrty_medium()  { tm_svrty_medium "$1"; [[ "$COLOR" -ge 2 ]] && html_out "<span style=\"color:#cd8000;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; }
tmln_svrty_medium(){ tm_svrty_medium "$1"; tmln_out; }
prln_svrty_medium(){ pr_svrty_medium "$1"; outln; }

tm_svrty_high()    { [[ "$COLOR" -ge 2 ]] && tm_out "\033[0;31m$1" || tm_bold "$1"; tm_off; }               # litered
pr_svrty_high()    { tm_svrty_high "$1"; [[ "$COLOR" -ge 2 ]] && html_out "<span style=\"color:#cd0000;\">$(html_reserved "$1")</span>" || ( [[ "$COLOR" -eq 1 ]] && html_out "<span style=\"font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")" ); }
tmln_svrty_high()  { tm_svrty_high "$1"; tmln_out; }
prln_svrty_high()  { pr_svrty_high "$1"; outln; }

tm_svrty_critical()   { [[ "$COLOR" -ge 2 ]] && tm_out "\033[1;31m$1" || tm_bold "$1"; tm_off; }           # red
pr_svrty_critical()   { tm_svrty_critical "$1"; [[ "$COLOR" -ge 2 ]] && html_out "<span style=\"color:red;font-weight:bold;\">$(html_reserved "$1")</span>" || ( [[ "$COLOR" -eq 1 ]] && html_out "<span style=\"font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")" ); }
tmln_svrty_critical() { tm_svrty_critical "$1"; tmln_out; }
prln_svrty_critical() { pr_svrty_critical "$1"; outln; }

tm_deemphasize()      { tm_out "$1"; }                                                                   # hook for a weakened screen output, see #600
pr_deemphasize()      { tm_deemphasize "$1"; html_out "<span style=\"color:darkgray;\">$(html_reserved "$1")</span>"; }
tmln_deemphasize()    { tm_deemphasize "$1"; tmln_out; }
prln_deemphasize()    { pr_deemphasize "$1"; outln; }

# color=1 functions
tm_off()        { [[ "$COLOR" -ne 0 ]] && tm_out "\033[m"; }

tm_bold()       { [[ "$COLOR" -ne 0 ]] && tm_out "\033[1m$1" || tm_out "$1"; tm_off; }
tmln_bold()     { tm_bold "$1"; tmln_out; }
pr_bold()       { tm_bold "$1"; [[ "$COLOR" -ne 0 ]] && html_out "<span style=\"font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; }
prln_bold()     { pr_bold "$1" ; outln; }

NO_ITALICS=false
if [[ $SYSTEM == OpenBSD ]]; then
     NO_ITALICS=true
elif [[ $SYSTEM == FreeBSD ]]; then
     if [[ ${SYSTEMREV%\.*} -le 9 ]]; then
          NO_ITALICS=true
     fi
fi
tm_italic()     { ( [[ "$COLOR" -ne 0 ]] && ! "$NO_ITALICS" ) && tm_out "\033[3m$1" || tm_out "$1"; tm_off; }
tmln_italic()   { tm_italic "$1" ; tmln_out; }
pr_italic()     { tm_italic "$1"; [[ "$COLOR" -ne 0 ]] && html_out "<i>$(html_reserved "$1")</i>" || html_out "$(html_reserved "$1")"; }
prln_italic()   { pr_italic "$1"; outln; }

tm_strikethru()   { [[ "$COLOR" -ne 0 ]] && tm_out "\033[9m$1" || tm_out "$1"; tm_off; }                          # ugly!
tmln_strikethru() { tm_strikethru "$1"; tmln_out; }
pr_strikethru()   { tm_strikethru "$1"; [[ "$COLOR" -ne 0 ]] && html_out "<strike>$(html_reserved "$1")</strike>" || html_out "$(html_reserved "$1")"; }
prln_strikethru() { pr_strikethru "$1" ; outln; }

tm_underline()    { [[ "$COLOR" -ne 0 ]] && tm_out "\033[4m$1" || tm_out "$1"; tm_off; }
tmln_underline()  { tm_underline "$1"; tmln_out; }
pr_underline()    { tm_underline "$1"; [[ "$COLOR" -ne 0 ]] && html_out "<u>$(html_reserved "$1")</u>" || html_out "$(html_reserved "$1")"; }
prln_underline()  { pr_underline "$1"; outln; }

tm_reverse()      { [[ "$COLOR" -ne 0 ]] && tm_out "\033[7m$1" || tm_out "$1"; tm_off; }
tm_reverse_bold() { [[ "$COLOR" -ne 0 ]] && tm_out "\033[7m\033[1m$1" || tm_out "$1"; tm_off; }
pr_reverse()      { tm_reverse "$1"; [[ "$COLOR" -ne 0 ]] && html_out "<span style=\"color:white;background-color:black;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; }
pr_reverse_bold() { tm_reverse_bold "$1"; [[ "$COLOR" -ne 0 ]] && html_out "<span style=\"color:white;background-color:black;font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; }

#pr_headline() { pr_blue "$1"; }
#https://misc.flogisoft.com/bash/tip_colors_and_formatting

#pr_headline() { [[ "$COLOR" -ge 2 ]] && out "\033[1;30m\033[47m$1" || out "$1"; tm_off; }
tm_headline()   { [[ "$COLOR" -ne 0 ]] && tm_out "\033[1m\033[4m$1" || tm_out "$1"; tm_off; }
tmln_headline() { tm_headline "$1"; tmln_out; }
pr_headline()   { tm_headline "$1"; [[ "$COLOR" -ne 0 ]] && html_out "<span style=\"text-decoration:underline;font-weight:bold;\">$(html_reserved "$1")</span>" || html_out "$(html_reserved "$1")"; }
pr_headlineln() { pr_headline "$1" ; outln; }

tm_squoted() { tm_out "'$1'"; }
pr_squoted() { out "'$1'"; }
tm_dquoted() { tm_out "\"$1\""; }
pr_dquoted() { out "\"$1\""; }

# either files couldn't be found or openssl isn't good enough (which shouldn't happen anymore)
tm_local_problem()   { tm_warning "Local problem: $1"; }
tmln_local_problem() { tmln_warning "Local problem: $1"; }
pr_local_problem()   { pr_warning "Local problem: $1"; }
prln_local_problem() { prln_warning "Local problem: $1"; }

# general failure
tm_fixme()   { tm_warning "Fixme: $1"; }
tmln_fixme() { tmln_warning "Fixme: $1"; }
pr_fixme()   { pr_warning "Fixme: $1"; }
prln_fixme() { prln_warning "Fixme: $1"; }

pr_url()     { tm_out "$1"; html_out "<a href=\"$1\" style=\"color:black;text-decoration:none;\">$1</a>"; }
pr_boldurl() { tm_bold "$1"; html_out "<a href=\"$1\" style=\"font-weight:bold;color:black;text-decoration:none;\">$1</a>"; }

### color switcher (see e.g. https://linuxtidbits.wordpress.com/2008/08/11/output-color-on-bash-scripts/
###                          https://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
### no output support for HTML!
set_color_functions() {
     local ncurses_tput=true

     if [[ $SYSTEM == OpenBSD ]] && [[ "$TERM" =~ xterm-256 ]]; then
          export TERM=xterm
          # OpenBSD can't handle 256 colors (yet) in xterm which might lead to ugly errors
          # like "tput: not enough arguments (3) for capability `AF'". Not our fault but
          # before we get blamed we fix it here.
     fi

     # Empty all vars if we have COLOR=0 equals no escape code -- these are globals:
     red=""
     green=""
     brown=""
     blue=""
     magenta=""
     cyan=""
     grey=""
     yellow=""
     off=""
     bold=""
     underline=""
     italic=""

     type -p tput &>/dev/null || return 0      # Hey wait, do we actually have tput / ncurses ?
     tput cols &>/dev/null || return 0         # tput under BSDs and GNUs doesn't work either (TERM undefined?)
     tput sgr0 &>/dev/null || ncurses_tput=false
     if [[ "$COLOR" -ge 2 ]]; then
          if $ncurses_tput; then
               red=$(tput setaf 1)
               green=$(tput setaf 2)
               brown=$(tput setaf 3)
               blue=$(tput setaf 4)
               magenta=$(tput setaf 5)
               cyan=$(tput setaf 6)
               grey=$(tput setaf 7)
               yellow=$(tput setaf 3; tput bold)
          else                                    # this is a try for old BSD, see terminfo(5)
               red=$(tput AF 1)
               green=$(tput AF 2)
               brown=$(tput AF 3)
               blue=$(tput AF 4)
               magenta=$(tput AF 5)
               cyan=$(tput AF 6)
               grey=$(tput AF 7)
               yellow=$(tput AF 3; tput md)
          fi
     fi
     if [[ "$COLOR" -ge 1 ]]; then
          if $ncurses_tput; then
               bold=$(tput bold)
               underline=$(tput sgr 0 1 2>/dev/null)
               italic=$(tput sitm)                # This doesn't work on FreeBSDi (9,10) and OpenBSD ...
               italic_end=$(tput ritm)            # ... and this, too
               off=$(tput sgr0)
          else                                    # this is a try for old BSD, see terminfo(5)
               bold=$(tput md)
               underline=$(tput us)
               italic=$(tput ZH 2>/dev/null)       # This doesn't work on FreeBSDi (9,10) and OpenBSD
               italic_end=$(tput ZR 2>/dev/null)   # ... probably entry missing in /etc/termcap
               reverse=$(tput mr)
               off=$(tput me)
          fi
     fi
     # FreeBSD 10 understands ESC codes like 'echo -e "\e[3mfoobar\e[23m"', but also no tput for italics
}

###### START universal helper function definitions ######

if [[ "${BASH_VERSINFO[0]}" == 3 ]]; then
     # 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
     toupper() { echo -n "${1^^}"; }
     tolower() { echo -n "${1,,}"; }
fi

get_last_char() {
     echo "${1:~0}"      # "${string: -1}" would work too (both also in bash 3.2)
}
                         # Checking for last char. If already a separator supplied, we don't need an additional one
debugme() {
     [[ "$DEBUG" -ge 2 ]] && "$@"
     return 0
}

hex2dec() {
     echo $((16#$1))
}

# convert 414243 into ABC
hex2ascii() {
          for (( i=0; i<${#1}; i+=2 )); do
               # 2>/dev/null added because 'warning: command substitution: ignored null byte in input'
               # --> didn't help though
               printf "\x${1:$i:2}" 2>/dev/null
          done
}

# convert decimal number < 256 to hex
dec02hex() {
     printf "x%02x" "$1"
}

# convert decimal number between 256 and < 256*256 to hex
dec04hex() {
     local a=$(printf "%04x" "$1")
     printf "x%02s, x%02s" "${a:0:2}" "${a:2:2}"
}


# trim spaces for BSD and old sed
count_lines() {
     echo $(wc -l <<< "$1")
}

count_words() {
     echo $(wc -w <<< "$1")
}

count_ciphers() {
     echo $(wc -w <<< "${1//:/ }")
}

newline_to_spaces() {
     tr '\n' ' ' <<< "$1" | sed 's/ $//'
}

colon_to_spaces() {
     echo "${1//:/ }"
}

strip_lf() {
     tr -d '\n' <<< "$1" | tr -d '\r'
}

strip_spaces() {
     echo "${1// /}"
}

# https://web.archive.org/web/20121022051228/http://codesnippets.joyent.com/posts/show/1816
strip_leading_space() {
     printf "%s" "${1#"${1%%[![:space:]]*}"}"
}
strip_trailing_space() {
     printf "%s" "${1%"${1##*[![:space:]]}"}"
}

is_number() {
     [[ "$1" =~ ^[1-9][0-9]*$ ]] && \
          return 0 || \
          return 1
}

strip_quote() {
     # remove color codes (see https://www.commandlinefu.com/commands/view/3584/remove-color-codes-special-characters-with-sed)
     #  \', leading and all trailing spaces
     sed -e "s,$(echo -e "\033")\[[0-9;]*[a-zA-Z],,g" \
          -e "s/\"/\\'/g" \
          -e 's/^ *//g' \
          -e 's/ *$//g' <<< "$1"
}

# " deconfuse vim\'s syntax highlighting ;-)


is_ipv4addr() {
     local octet="(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])"
     local ipv4address="$octet\\.$octet\\.$octet\\.$octet"

     [[ -z "$1" ]] && return 1
     # more than numbers, important for hosts like AAA.BBB.CCC.DDD.in-addr.arpa.DOMAIN.TLS
     [[ -n $(tr -d '0-9\.' <<< "$1") ]] && return 1

     grep -Eq "$ipv4address" <<< "$1" && \
          return 0 || \
          return 1
}

# a bit easier
is_ipv6addr() {
     [[ -z "$1" ]] && return 1
     # less than 2x ":"
     [[ $(count_lines "$(tr ':' '\n' <<< "$1")") -le 1 ]] && \
          return 1
     # check which chars allowed:
     [[ -n "$(tr -d '0-9:a-fA-F ' <<< "$1" | sed -e '/^$/d')" ]] && \
          return 1
     return 0
}

###### END universal helper function definitions ######

###### START ServerHello/OpenSSL/F5 function definitions ######

#arg1: TLS 1.2 and below ciphers
#arg2: TLS 1.3 ciphers
#arg3: options (e.g., -V)
actually_supported_osslciphers() {
     local ciphers="$1"
     local tls13_ciphers="$TLS13_OSSL_CIPHERS"

     [[ "$2" != ALL ]] && tls13_ciphers="$2"
     "$HAS_SECLEVEL" && [[ -n "$ciphers" ]] && ciphers="@SECLEVEL=0:$1"
     if "$HAS_CIPHERSUITES"; then
          $OPENSSL ciphers $3 $OSSL_CIPHERS_S -ciphersuites "$tls13_ciphers" "$ciphers" 2>/dev/null || echo ""
     elif [[ -n "$tls13_ciphers" ]]; then
          $OPENSSL ciphers $3 $OSSL_CIPHERS_S "$tls13_ciphers:$ciphers" 2>/dev/null || echo ""
     else
          $OPENSSL ciphers $OSSL_CIPHERS_S $3 "$ciphers" 2>/dev/null || echo ""
     fi
}

# Given a protocol (arg1) and a list of ciphers (arg2) that is formatted as
# ", xx,xx, xx,xx, xx,xx, xx,xx" remove any TLSv1.3 ciphers if the protocol
# is less than 04 and remove any TLSv1.2-only ciphers if the protocol is less
# than 03.
strip_inconsistent_ciphers() {
     local -i proto=0x$1
     local cipherlist="$2"

     [[ $proto -lt 4 ]] && cipherlist="${cipherlist//, 13,0[0-9a-fA-F]/}"
     if [[ $proto -lt 3 ]]; then
          cipherlist="${cipherlist//, 00,3[b-fB-F]/}"
          cipherlist="${cipherlist//, 00,40/}"
          cipherlist="${cipherlist//, 00,6[7-9a-dA-D]/}"
          cipherlist="${cipherlist//, 00,9[c-fC-F]/}"
          cipherlist="${cipherlist//, 00,[abAB][0-9a-fA-F]/}"
          cipherlist="${cipherlist//, 00,[cC][0-5]/}"
          cipherlist="${cipherlist//, 16,[bB][7-9aA]/}"
          cipherlist="${cipherlist//, [cC]0,2[3-9a-fA-F]/}"
          cipherlist="${cipherlist//, [cC]0,3[01278a-fA-F]/}"
          cipherlist="${cipherlist//, [cC]0,[4-9aA][0-9a-fA-F]/}"
          cipherlist="${cipherlist//, [cC][cC],1[345]/}"
          cipherlist="${cipherlist//, [cC][cC],[aA][89a-eA-E]/}"
     fi
     echo "$cipherlist"
     return 0
}

# retrieve cipher from ServerHello (via openssl)
get_cipher() {
     local cipher=""
     local server_hello="$(cat -v "$1")"
     # This and two other following instances are not best practice and normally a useless use of "cat", see
     # https://web.archive.org/web/20160711205930/http://porkmail.org/era/unix/award.html#uucaletter
     # However there seem to be cases where the preferred  $(< "$1")  logic has a problem.
     # Esepcially with bash 3.2 (Mac OS X) and when on the server side binary chars
     # are returned, see https://stackoverflow.com/questions/7427262/how-to-read-a-file-into-a-variable-in-shell#22607352
     # and https://github.com/drwetter/testssl.sh/issues/1292
     # Performance measurements showed no to barely measureable penalty (1s displayed in 9 tries).

     if [[ "$server_hello" =~ Cipher\ *:\ ([A-Z0-9]+-[A-Za-z0-9\-]+|TLS_[A-Za-z0-9_]+|SSL_[A-Za-z0-9_]+) ]]; then
          cipher="${BASH_REMATCH##* }"
     elif [[ "$server_hello" =~ (New|Reused)", "(SSLv[23]|TLSv1(\.[0-3])?(\/SSLv3)?)", Cipher is "([A-Z0-9]+-[A-Za-z0-9\-]+|TLS_[A-Za-z0-9_]+) ]]; then
          cipher="${BASH_REMATCH##* }"
     fi
     tm_out "$cipher"
}

# retrieve protocol from ServerHello (via openssl)
get_protocol() {
     local protocol=""
     local server_hello="$(cat -v "$1")"

     if [[ "$server_hello" =~ Protocol\ *:\ (SSLv[23]|TLSv1(\.[0-3])?) ]]; then
          protocol="${BASH_REMATCH##* }"
     elif [[ "$server_hello" =~ (New|Reused)", TLSv1.3, Cipher is "TLS_[A-Z0-9_]+ ]]; then
          # Note: When OpenSSL prints "New, <protocol>, Cipher is <cipher>", <cipher> is the
          # negotiated cipher, but <protocol> is not the negotiated protocol. Instead, it is
          # the SSL/TLS protocol that first defined <cipher>. Since the ciphers that were
          # first defined for TLSv1.3 may only be used with TLSv1.3, this line may be used
          # to determine whether TLSv1.3 was negotiated, but if another protocol is specified
          # on this line, then this line does not indicate the actual protocol negotiated. Also,
          # only TLSv1.3 cipher suites have names that begin with TLS_, which provides additional
          # assurance that the above match will only succeed if TLSv1.3 was negotiated.
          protocol="TLSv1.3"
     fi
     tm_out "$protocol"
}

# now some function for the integrated BIGIP F5 Cookie detector (see https://github.com/drwetter/F5-BIGIP-Decoder)

f5_hex2ip() {
     debugme echo "$1"
     echo $((16#${1:0:2})).$((16#${1:2:2})).$((16#${1:4:2})).$((16#${1:6:2}))
}
f5_hex2ip6() {
     debugme echo "$1"
     echo "[${1:0:4}:${1:4:4}:${1:8:4}:${1:12:4}.${1:16:4}:${1:20:4}:${1:24:4}:${1:28:4}]"
}

f5_determine_routeddomain() {
     local tmp
     tmp="${1%%o*}"
     echo "${tmp/rd/}"
}

f5_ip_oldstyle() {
     local tmp
     local a b c d

     tmp="${1/%.*}"                     # until first dot
     tmp="$(printf "%08x" "$tmp")"      # convert the whole thing to hex, now back to ip (reversed notation:
     tmp="$(f5_hex2ip $tmp)"            # transform to ip with reversed notation
     IFS="." read -r a b c d <<< "$tmp" # reverse it
     echo $d.$c.$b.$a
}

f5_port_decode() {
     local tmp

     tmp="$(strip_lf "$1")"             # remove lf if there is one
     tmp="${tmp/.0000/}"                # to be sure remove trailing zeros with a dot
     tmp="${tmp#*.}"                    # get the port
     tmp="$(printf "%04x" "${tmp}")"    # to hex
     if [[ ${#tmp} -eq 4 ]]; then
          :
     elif [[ ${#tmp} -eq 3 ]]; then     # fill it up with leading zeros if needed
          tmp=0${tmp}
     elif [[ ${#tmp} -eq 2 ]]; then
          tmp=00${tmp}
     fi
     echo $((16#${tmp:2:2}${tmp:0:2}))  # reverse order and convert it from hex to dec
}

###### END universal helper function definitions ######


###### START scoring function definitions ######

# Sets the grade cap to ARG1
# arg1: A grade to set ("A", "B", "C", "D", "E", "F", "M", or "T")
# arg2: A reason why (e.g. "Vulnerable to CRIME")
set_grade_cap() {
     "$do_rating" || return 0
     GRADE_CAP_REASONS+=("Grade capped to $1. $2")

     # Always set special attributes. These are hard caps, due to name mismatch or cert being invalid
     if [[ "$1" == T || "$1" == M ]]; then
          GRADE_CAP="$1"
     # Only keep track of the lowest grade cap, since a higher grade cap wont do anything (F = lowest, A = highest)
     elif  [[ ! "$GRADE_CAP" > "$1" ]]; then
          GRADE_CAP="$1"
     fi
     return 0
}

# Sets a grade warning, as specified by the grade specification
# arg1: A warning message
set_grade_warning() {
     "$do_rating" || return 0
     GRADE_WARNINGS+=("$1")
     return 0
}

# Sets the score for Category 2 (Key Exchange Strength)
# arg1: Short key algorithm ("EC", "DH", "RSA", ...) # Can die, when we get DH_PARAMs
# arg2: key size (number of bits)
set_key_str_score() {
     local type=$1
     local size=$2

     "$do_rating" || return 0

     # TODO: We need to get the size of DH params (follows the same table as the "else" clause)
     # For now, verifying the key size will do...
     if [[ $type == EC ]]; then
          if [[ $size -lt 110 ]] && [[ $KEY_EXCH_SCORE -gt 20 ]]; then
               let KEY_EXCH_SCORE=20
               set_grade_cap "F" "Using an insecure key"
          elif [[ $size -lt 123 ]] && [[ $KEY_EXCH_SCORE -gt 40 ]]; then
               let KEY_EXCH_SCORE=40
               set_grade_cap "F" "Using an insecure key"
          elif [[ $size -lt 163 ]] && [[ $KEY_EXCH_SCORE -gt 80 ]]; then
               let KEY_EXCH_SCORE=80
               set_grade_cap "B" "Using a weak key"
          elif [[ $size -lt 225 ]] && [[ $KEY_EXCH_SCORE -gt 90 ]]; then
               let KEY_EXCH_SCORE=90
          fi
     else
          if [[ $size -lt 512 ]] && [[ $KEY_EXCH_SCORE -gt 20 ]]; then
               let KEY_EXCH_SCORE=20
               set_grade_cap "F" "Using an insecure key"
          elif [[ $size -lt 1024 ]] && [[ $KEY_EXCH_SCORE -gt 40 ]]; then
               let KEY_EXCH_SCORE=40
               set_grade_cap "F" "Using an insecure key"
          elif [[ $size -lt 2048 ]] && [[ $KEY_EXCH_SCORE -gt 80 ]]; then
               let KEY_EXCH_SCORE=80
               set_grade_cap "B" "Using a weak key"
          elif [[ $size -lt 4096 ]] && [[ $KEY_EXCH_SCORE -gt 90 ]]; then
               let KEY_EXCH_SCORE=90
          fi
     fi
     return 0
}

# Sets the best and worst bit size key, used to grade Category 3 (Cipher Strength)
# This function itself doesn't actually set a score; its just in the name to keep it logical (score == grading function)
# arg1: a bit size
set_ciph_str_score() {
     local size=$1

     "$do_rating" || return 0

     [[ $size -gt $CIPH_STR_BEST ]] && let CIPH_STR_BEST=$size
     [[ $size -lt $CIPH_STR_WORST ]] && let CIPH_STR_WORST=$size
     return 0
}

###### END scoring function definitions ######

##################### START output file formatting functions #########################
#################### START JSON file functions ####################

fileout_json_footer() {
     if "$do_json"; then
          if [[ "$SCAN_TIME" -eq 0 ]]; then
               fileout_json_finding "scanTime" "WARN" "Scan interrupted" "" "" ""
          elif [[ $SEVERITY_LEVEL -lt $LOW ]] ; then
               # no scan time in --severity=low and above, also needed for Travis, hackish...
               fileout_json_finding "scanTime" "INFO" $SCAN_TIME "" "" ""
          fi
          printf "]\n" >> "$JSONFILE"
     fi
     if "$do_pretty_json"; then
          if [[ "$SCAN_TIME" -eq 0 ]]; then
               echo -e "          ],\n                    \"scanTime\"  : \"Scan interrupted\"\n}" >> "$JSONFILE"
          else
               echo -e "          ],\n                    \"scanTime\"  : ${SCAN_TIME}\n}" >> "$JSONFILE"
          fi
     fi
}

fileout_json_section() {
     case $1 in
           0) echo -e    "                    \"pretest\"           : [" ;;
           1) echo -e    "                    \"singleCipher\"      : [" ;;
           2) echo -e ",\n                    \"protocols\"         : [" ;;
           3) echo -e ",\n                    \"grease\"            : [" ;;
           4) echo -e ",\n                    \"ciphers\"           : [" ;;
           5) echo -e ",\n                    \"fs\"                : [" ;;
           6) echo -e ",\n                    \"serverPreferences\" : [" ;;
           7) echo -e ",\n                    \"serverDefaults\"    : [" ;;
           8) echo -e ",\n                    \"headerResponse\"    : [" ;;
           9) echo -e ",\n                    \"vulnerabilities\"   : [" ;;
          10) echo -e ",\n                    \"cipherTests\"       : [" ;;
          11) echo -e ",\n                    \"browserSimulations\": [" ;;
          12) echo -e ",\n                    \"rating\"            : [" ;;
           *) echo "invalid section" ;;
     esac
}

fileout_section_header() {
     local str=""
     "$2" && str="$(fileout_section_footer false)"
     "$do_pretty_json" && FIRST_FINDING=true && (printf "%s%s\n" "$str" "$(fileout_json_section "$1")") >> "$JSONFILE"
     SECTION_FOOTER_NEEDED=true
}

# arg1: whether to end object too
fileout_section_footer() {
     "$do_pretty_json" && printf "\n                    ]" >> "$JSONFILE"
     "$do_pretty_json" && "$1" && echo -e "\n          }" >> "$JSONFILE"
     SECTION_FOOTER_NEEDED=false
}

fileout_json_print_parameter() {
     local parameter="$1"
     local filler="$2"
     local value="$3"
     local not_last="$4"
     local spaces=""

     "$do_json" && \
          spaces="              " || \
          spaces="                                "
     if [[ -n "$value" ]] || [[ "$parameter" == finding ]]; then
          printf "%s%s%s%s" "$spaces" "\"$parameter\"" "$filler" ": \"$value\"" >> "$JSONFILE"
          "$not_last" && printf ",\n" >> "$JSONFILE"
     fi
}

fileout_json_finding() {
     local target
     local finding="$3"
     local cve="$4"
     local cwe="$5"
     local hint="$6"

     if "$do_json"; then
          "$FIRST_FINDING" || echo -n "," >> "$JSONFILE"
          echo -e "         {"  >> "$JSONFILE"
          fileout_json_print_parameter "id" "           " "$1" true
          fileout_json_print_parameter "ip" "           " "$NODE/$NODEIP" true
          fileout_json_print_parameter "port" "         " "$PORT" true
          fileout_json_print_parameter "severity" "     " "$2" true
          fileout_json_print_parameter "cve" "          " "$cve" true
          fileout_json_print_parameter "cwe" "          " "$cwe" true
          "$GIVE_HINTS" && fileout_json_print_parameter "hint" "         " "$hint" true
          fileout_json_print_parameter "finding" "      " "$finding" false
          echo -e "\n          }" >> "$JSONFILE"
     fi
     if "$do_pretty_json"; then
          if [[ "$1" == service ]]; then
               if [[ $SERVER_COUNTER -gt 1 ]]; then
                    echo "          ," >> "$JSONFILE"
               fi
               target="$NODE"
               $do_mx_all_ips && target="$URI"
               echo -e "          {
                    \"targetHost\"      : \"$target\",
                    \"ip\"              : \"$NODEIP\",
                    \"port\"            : \"$PORT\",
                    \"rDNS\"            : \"$rDNS\",
                    \"service\"         : \"$finding\"," >> "$JSONFILE"
               $do_mx_all_ips && echo -e "                    \"hostname\"        : \"$NODE\","  >> "$JSONFILE"
          else
               ("$FIRST_FINDING" && echo -n "                            {" >> "$JSONFILE") || echo -n ",{" >> "$JSONFILE"
               echo -e -n "\n"  >> "$JSONFILE"
               fileout_json_print_parameter "id" "           " "$1" true
               fileout_json_print_parameter "severity" "     " "$2" true
               fileout_json_print_parameter "cve" "          " "$cve" true
               fileout_json_print_parameter "cwe" "          " "$cwe" true
               "$GIVE_HINTS" && fileout_json_print_parameter "hint" "         " "$hint" true
               fileout_json_print_parameter "finding" "      " "$finding" false
               echo -e -n "\n                           }" >> "$JSONFILE"
          fi
     fi
}

fileout_pretty_json_banner() {
     local target

     if ! "$do_mass_testing"; then
          [[ -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"
     fi

     echo -e "          \"Invocation\"  : \"$PROG_NAME $CMDLINE\",
          \"at\"          : \"$HNAME:$OPENSSL_LOCATION\",
          \"version\"     : \"$VERSION $GIT_REL_SHORT\",
          \"openssl\"     : \"$OSSL_NAME $OSSL_VER from $OSSL_BUILD_DATE\",
          \"startTime\"   : \"$START_TIME\",
          \"scanResult\"  : ["
}

fileout_banner() {
     if "$JSONHEADER"; then
          # "$do_json" &&                    # here we maybe should add a banner, too
          "$do_pretty_json" && (printf "%s\n" "$(fileout_pretty_json_banner)") >> "$JSONFILE"
     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
     fi
     # CSV: no footer
     return 0
}

fileout_insert_warning() {
     # See #815. Make sure we don't mess up the JSON PRETTY format if we complain with a client side warning.
     # This should only be called if an *extra* warning will be printed (previously: 'fileout <extra_warning_ID> "WARN" '
     # arg1: json identifier,  arg2: normally "WARN",  arg3: finding
     #
     # Also, we have to be careful with any form of mass testing so that a warning won't lead to an invalid JSON
     # file. As any child will do any check as well (to be reconsidered later), we don't need also the parent to issue
     # warnings upfront, see #1169. As a detection we'll use --file/-iL as in the children jobs it'll be removed:
     [[ "$CMDLINE=" =~ --file ]] && return 0
     [[ "$CMDLINE=" =~ -iL ]] && return 0
     # Note we still have the message on screen + in HTML which is not as optimal as it could be

     if "$do_pretty_json"; then
          echo -e "          \"clientProblem${CLIENT_PROB_NO}\" : [" >>"$JSONFILE"
          CLIENT_PROB_NO=$((CLIENT_PROB_NO + 1))
          FIRST_FINDING=true       # make sure we don't have a comma here
     fi
     fileout "$1" "$2" "$3"
     if "$do_pretty_json"; then
          echo -e "\n          ]," >>"$JSONFILE"
     fi
}

fileout_csv_finding() {
     safe_echo "\"$1\"," >> "$CSVFILE"
     safe_echo "\"$2\"," >> "$CSVFILE"
     safe_echo "\"$3\"," >> "$CSVFILE"
     safe_echo "\"$4\"," >> "$CSVFILE"
     safe_echo "\"$5\"," >> "$CSVFILE"
     safe_echo "\"$6\"," >> "$CSVFILE"
     if "$GIVE_HINTS"; then
          safe_echo "\"$7\"," >> "$CSVFILE"
          safe_echo "\"$8\"\n" >> "$CSVFILE"
     else
          safe_echo "\"$7\"\n" >> "$CSVFILE"
     fi
}


# ID, SEVERITY, FINDING, CVE, CWE, HINT
fileout() {
     local severity="$2"
     local cve="$4"
     local cwe="$5"
     local hint="$6"

     if ( "$do_pretty_json" && [[ "$1" == service ]] ) || show_finding "$severity"; then
          local finding=$(strip_lf "$(newline_to_spaces "$(strip_quote "$3")")")           # additional quotes will mess up screen output
          [[ -e "$JSONFILE" ]] && [[ ! -d "$JSONFILE" ]] && fileout_json_finding "$1" "$severity" "$finding" "$cve" "$cwe" "$hint"
          "$do_csv" && [[ -n "$CSVFILE" ]] && [[ ! -d "$CSVFILE" ]] && \
               fileout_csv_finding "$1" "$NODE/$NODEIP" "$PORT" "$severity" "$finding" "$cve" "$cwe" "$hint"
          "$FIRST_FINDING" && FIRST_FINDING=false
     fi
}


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_display_only"; then
          fname_prefix="local-ciphers"
     elif "$do_mass_testing"; then
          :
     elif "$do_mx_all_ips"; then
          fname_prefix="${FNAME_PREFIX}mx-${URI}"
     else
          # ensure NODE, URL_PATH, PORT, IPADDR and IP46ADDR are set
          ! "$filename_provided" && [[ -z "$NODE" ]] && parse_hn_port "${URI}"
          fname_prefix="${FNAME_PREFIX}${NODE}_p${PORT}"
     fi
     if [[ -z "$JSONFILE" ]]; then
          JSONFILE="$fname_prefix-$(date +"%Y%m%d-%H%M".json)"
     elif [[ -d "$JSONFILE" ]]; then
          JSONFILE="$JSONFILE/${fname_prefix}-$(date +"%Y%m%d-%H%M".json)"
     fi
     # Silently reset APPEND var if the file doesn't exist as otherwise it won't be created
     if "$APPEND" && [[ ! -s "$JSONFILE" ]]; then
          APPEND=false
     fi
     if "$APPEND"; then
          JSONHEADER=false
     else
          [[ -s "$JSONFILE" ]] && fatal "non-empty \"$JSONFILE\" exists. Either use \"--append\" or (re)move it" $ERR_FCREATE
          "$do_json" && echo "[" > "$JSONFILE"
          "$do_pretty_json" && echo "{" > "$JSONFILE"
     fi
     return 0
}


csv_header() {
     local fname_prefix
     local filename_provided=false

     [[ -n "$CSVFILE" ]] && [[ ! -d "$CSVFILE" ]] && filename_provided=true
     # CSV similar to JSON
     ! "$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
          :
     elif "$do_mx_all_ips"; then
          fname_prefix="${FNAME_PREFIX}mx-${URI}"
     else
          # ensure NODE, URL_PATH, PORT, IPADDR and IP46ADDR are set
          ! "$filename_provided" && [[ -z "$NODE" ]] && parse_hn_port "${URI}"
          fname_prefix="${FNAME_PREFIX}${NODE}_p${PORT}"
     fi
     if [[ -z "$CSVFILE" ]]; then
          CSVFILE="${fname_prefix}-$(date +"%Y%m%d-%H%M".csv)"
     elif [[ -d "$CSVFILE" ]]; then
          CSVFILE="$CSVFILE/${fname_prefix}-$(date +"%Y%m%d-%H%M".csv)"
     fi
     # Silently reset APPEND var if the file doesn't exist as otherwise it won't be created
     if "$APPEND" && [[ ! -s "$CSVFILE" ]]; then
          APPEND=false
     fi
     if "$APPEND"; then
          CSVHEADER=false
     else
          [[ -s "$CSVFILE" ]] && fatal "non-empty \"$CSVFILE\" exists. Either use \"--append\" or (re)move it" $ERR_FCREATE
          touch "$CSVFILE"
          if "$GIVE_HINTS"; then
               fileout_csv_finding "id" "fqdn/ip" "port" "severity" "finding" "cve" "cwe" "hint"
          else
               fileout_csv_finding "id" "fqdn/ip" "port" "severity" "finding" "cve" "cwe"
          fi
     fi
     return 0
}


################# END JSON file functions. START HTML functions ####################

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.
     ! "$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"
     elif "$do_mass_testing"; then
          :
     elif "$do_mx_all_ips"; then
          fname_prefix="${FNAME_PREFIX}mx-${URI}"
     else
          # ensure NODE, URL_PATH, PORT, IPADDR and IP46ADDR are set
          ! "$filename_provided" && [[ -z "$NODE" ]] && parse_hn_port "${URI}"
          fname_prefix="${FNAME_PREFIX}${NODE}_p${PORT}"
     fi
     if [[ -z "$HTMLFILE" ]]; then
          HTMLFILE="$fname_prefix-$(date +"%Y%m%d-%H%M".html)"
     elif [[ -d "$HTMLFILE" ]]; then
          HTMLFILE="$HTMLFILE/$fname_prefix-$(date +"%Y%m%d-%H%M".html)"
     fi
     # Silently reset APPEND var if the file doesn't exist as otherwise it won't be created
     if "$APPEND" && [[ ! -s "$HTMLFILE" ]]; then
          APPEND=false
     fi
     if "$APPEND"; then
          HTMLHEADER=false
     else
          [[ -s "$HTMLFILE" ]] && fatal "non-empty \"$HTMLFILE\" exists. Either use \"--append\" or (re)move it" $ERR_FCREATE
          html_out "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
          html_out "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
          html_out "<!-- This file was created with testssl.sh. https://testssl.sh -->\n"
          html_out "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
          html_out "<head>\n"
          html_out "<meta http-equiv=\"Content-Type\" content=\"application/xml+xhtml; charset=UTF-8\" />\n"
          html_out "<title>testssl.sh</title>\n"
          html_out "</head>\n"
          html_out "<body>\n"
          html_out "<pre>\n"
     fi
     return 0
}

html_banner() {
     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"
          html_out "## version openssl: \"$OSSL_NAME $OSSL_VER\" from \"$OSSL_BUILD_DATE\")\n\n"
     fi
}

html_footer() {
     if "$HTMLHEADER"; then
          html_out "</pre>\n"
          html_out "</body>\n"
          html_out "</html>\n"
     fi
     return 0
}

################# END HTML file functions ####################

prepare_logging() {
     # arg1: for testing mx records name we put a name of logfile in here, otherwise we get strange file names
     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="${FNAME_PREFIX}${NODE}_p${PORT}"

     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
          [[ -s "$LOGFILE" ]] && fatal "non-empty \"$LOGFILE\" exists. Either use \"--append\" or (re)move it" $ERR_FCREATE
     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 -i "$LOGFILE")
}

################### END all file output functions #########################

# prints out multiple lines in $1, left aligned by spaces in $2
out_row_aligned() {
     local first=true

     while read line; do
          "$first" && \
               first=false || \
               out "$2"
          outln "$line"
     done <<< "$1"
}

# prints text over multiple lines, trying to make no line longer than $max_width.
# Each line is indented with $spaces.
out_row_aligned_max_width() {
     local text="$1"
     local spaces="$2"
     local -i max_width="$3"
     local -i i len
     local cr=$'\n'
     local line
     local first=true

     max_width=$max_width-${#spaces}
     len=${#text}
     while true; do
          if [[ $len -lt $max_width ]]; then
               # If the remaining text to print is shorter than $max_width,
               # then just print it.
               i=$len
          else
               # 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 ! "$first"; then
               tm_out "${cr}${spaces}"
          fi
          tm_out "${text:0:i}"
          [[ $i -eq $len ]] && break
          len=$len-$i-1
          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"
}

print_fixed_width() {
     local text="$1"
     local -i i len width="$2"
     local print_function="$3"

     len=${#text}
     $print_function "$text"
     for (( i=len; i <= width; i++ )); do
          out " "
     done
}

# saves $TMPFILE or file supplied in $2 under name "$TEMPDIR/$NODEIP.$1".
# Note: after finishing $TEMPDIR will be removed unless DEBUG >=1
tmpfile_handle() {
     local savefile="$2"
     [[ -z "$savefile" ]] && savefile=$TMPFILE
#FIXME: make sure/find out if we do not need $TEMPDIR/$NODEIP.$1" if debug=0. We would save fs access here
     mv $savefile "$TEMPDIR/$NODEIP.$1" 2>/dev/null
     [[ $ERRFILE =~ dev.null ]] && return 0 || \
          mv $ERRFILE "$TEMPDIR/$NODEIP.${1//.txt/}.errorlog" 2>/dev/null
     return 0
}

# arg1: line with comment sign, tabs and so on
filter_input() {
     sed -e 's/#.*$//' -e '/^$/d' <<< "$1" | tr -d '\n' | tr -d '\t' | tr -d '\r'
}

# Dl's any URL (arg1) via HTTP 1.1 GET from port 80, arg2: file to store http body.
# Proxy is not honored yet (see cmd line switches) -- except when using curl or wget.
# There the environment variable is used automatically
# Currently it is being used by check_revocation_crl() only.
http_get() {
     local proto z
     local node="" query=""
     local dl="$2"
     local useragent="$UA_STD"
     local jsonID="http_get"

     "$SNEAKY" && useragent="$UA_SNEAKY"

     if type -p curl &>/dev/null; then
          if [[ -z "$PROXY" ]]; then
               curl -s --noproxy '*' -A $''"$useragent"'' -o $dl "$1"
          else
               # for the sake of simplicity assume the proxy is using http
               curl -s -x $PROXYIP:$PROXYPORT -A $''"$useragent"'' -o $dl "$1"
          fi
          return $?
     elif type -p wget &>/dev/null; then
          # wget has no proxy command line. We need to use http_proxy instead. And for the sake of simplicity
          # assume the GET protocol we query is using http -- http_proxy is the $ENV not for the connection TO
          # the proxy, but for the protocol we query THROUGH the proxy
          if [[ -z "$PROXY" ]]; then
               wget --no-proxy -q -U $''"$useragent"'' -O $dl "$1"
          else
               if [[ -z "$http_proxy" ]]; then
                    http_proxy=http://$PROXYIP:$PROXYPORT wget -q -U $''"$useragent"'' -O $dl "$1"
               else
                    wget -q -U $''"$useragent"'' -O $dl "$1"
               fi
          fi
          return $?
     else
          # Worst option: slower and hiccups with chunked transfers. Workaround for the
          # latter is using HTTP/1.0. We do not support https here, yet.
          # First the URL will be split
          IFS=/ read -r proto z node query <<< "$1"
          proto=${proto%:}
          if [[ "$proto" != http ]]; then
               pr_warning "protocol $proto not supported yet"
               fileout "$jsonID" "DEBUG" "protocol $proto not supported yet"
               return 6
          fi
          if [[ -n $PROXY ]]; then
               # PROXYNODE works better than PROXYIP on modern versions of squid. \
               # We don't reuse the code in fd_socket() as there's initial CONNECT which makes problems
               if ! exec 33<> /dev/tcp/${PROXYNODE}/${PROXYPORT}; then
                    outln
                    pr_warning "$PROG_NAME: unable to open a socket to proxy $PROXYNODE:$PROXYPORT"
                    fileout "$jsonID" "DEBUG" "$PROG_NAME: unable to open a socket to proxy $PROXYNODE:$PROXYPORT"
                    return 6
               else
                    printf -- "%b" "GET $proto://$node/$query HTTP/1.0\r\nUser-Agent: $useragent\r\nHost: $node\r\nAccept: */*\r\n\r\n" >&33
               fi
          else
               IFS=/ read -r proto z node query <<< "$1"
               exec 33<>/dev/tcp/$node/80
               printf -- "%b" "GET /$query HTTP/1.0\r\nUser-Agent: $useragent\r\nHost: $node\r\nAccept: */*\r\n\r\n" >&33
          fi
          # Strip HTTP header. When in Debug Mode we leave the raw data in place
          if [[ $DEBUG -ge 1 ]]; then
               cat <&33 >${dl}.raw
               cat ${dl}.raw | sed '1,/^[[:space:]]*$/d' >${dl}
          else
               cat <&33 | sed '1,/^[[:space:]]*$/d' >${dl}
          fi
          exec 33<&-
          exec 33>&-
          [[ -s "$dl" ]] && return 0 || return 1
     fi
}

# Outputs the headers when downloading any URL (arg1) via HTTP 1.1 GET from port 80.
# Only works if curl or wget is available.
# There the environment variable is used automatically
# Currently it is being used by check_pwnedkeys() only.
http_get_header() {
     local proto z
     local node="" query=""
     local dl="$2"
     local useragent="$UA_STD"
     local jsonID="http_get_header"
     local headers
     local -i ret

     "$SNEAKY" && useragent="$UA_SNEAKY"

     if type -p curl &>/dev/null; then
          if [[ -z "$PROXY" ]]; then
               headers="$(curl --head -s  --noproxy '*' -A $''"$useragent"'' "$1")"
          else
               # for the sake of simplicity assume the proxy is using http
               headers="$(curl --head -s -x $PROXYIP:$PROXYPORT -A $''"$useragent"'' "$1")"
          fi
          ret=$?
          [[ $ret -eq 0 ]] && tm_out "$headers"
          return $ret
     elif type -p wget &>/dev/null; then
          # wget has no proxy command line. We need to use http_proxy instead. And for the sake of simplicity
          # assume the GET protocol we query is using http -- http_proxy is the $ENV not for the connection TO
          # the proxy, but for the protocol we query THROUGH the proxy
          if [[ -z "$PROXY" ]]; then
               headers="$(wget --no-proxy -q -S -U $''"$useragent"'' -O /dev/null "$1" 2>&1)"
          else
               if [[ -z "$http_proxy" ]]; then
                    headers="$(http_proxy=http://$PROXYIP:$PROXYPORT wget -q -S  -U $''"$useragent"'' -O /dev/null "$1" 2>&1)"
               else
                    headers="$(wget -q -S -U $''"$useragent"'' -O /dev/null "$1" 2>&1)"
               fi
          fi
          ret=$?
          [[ $ret -eq 0 ]] && tm_out "$headers"
          # wget(1): "8: Server issued an error response.". Happens e.g. when 404 is returned. However also if the call wasn't correct (400)
          # So we assume for now that everything is submitted correctly. We parse the error code too later
          [[ $ret -eq 8 ]] && ret=0 && tm_out "$headers"
          return $ret
     else
          return 1
     fi
}

ldap_get() {
     local ldif
     local -i success
     local crl="$1"
     local tmpfile="$2"
     local jsonID="$3"

     if type -p curl &>/dev/null; then
          # proxy handling?
          ldif="$(curl -s "$crl")"
          [[ $? -eq 0 ]] || return 1
          awk '/certificateRevocationList/ { print $2 }' <<< "$ldif" | $OPENSSL base64 -d -A -out "$tmpfile" 2>/dev/null
          [[ -s "$tmpfile" ]] || return 1
          return 0
     else
          pr_litecyan " (for LDAP CRL check install \"curl\")"
          fileout "$jsonID" "INFO" "LDAP CRL revocation check needs \"curl\""
          return 2
     fi
}

# checks whether the public key in arg1 appears in the https://pwnedkeys.com/ database.
# arg1: file containing certificate
# arg2: public key algorithm
# arg3 key size
# Responses are as follows:
#     0 - not checked
#     1 - key not found in database
#     2 - key found in database
#     7 - network/proxy failure
check_pwnedkeys() {
     local cert="$1"
     local cert_key_algo="$2"
     local -i cert_keysize="$3"
     local pubkey curve response

     "$PHONE_OUT" || return 0

     # https://pwnedkeys.com only keeps records on 1024 bit and larger RSA keys,
     # as well as elliptic-curve keys on the P-256, P-384, and P-521 curves.
     if [[ "$cert_key_algo" =~ RSA ]] || [[ "$cert_key_algo" =~ rsa ]]; then
          [[ $cert_keysize -ge 1024 ]] || return 0
     elif [[ "$cert_key_algo" =~ ecdsa ]] || [[ "$cert_key_algo" == *ecPublicKey ]]; then
          [[ $cert_keysize -eq 256 ]] || [[ $cert_keysize -eq 384 ]] || \
               [[ $cert_keysize -eq 521 ]] || return 0
     else
          return 0
     fi

     pubkey="$($OPENSSL x509 -in "$cert" -pubkey -noout 2>/dev/null)"
     # If it is an elliptic curve key, check that it is P-256, P-384, or P-521.
     if [[ "$cert_key_algo" =~ ecdsa ]] || [[ "$cert_key_algo" == *ecPublicKey ]]; then
          curve="$($OPENSSL ec -pubin -text <<< "$pubkey" 2>/dev/null)"
          curve="${curve#*ASN1 OID: }"
          [[ "$curve" == prime256v1* ]] || [[ "$curve" == secp384r1* ]] || \
               [[ "$curve" == secp521r1* ]] || return 0
     fi
     fingerprint="$($OPENSSL pkey -pubin -outform DER <<< "$pubkey" 2>/dev/null | $OPENSSL dgst -sha256 -hex 2>/dev/null)"
     fingerprint="${fingerprint#*= }"
     response="$(http_get_header "https://v1.pwnedkeys.com/$fingerprint")"
     # Handle curl's/wget's connectivity exit codes
     case $? in
          4|5|7)     return 7 ;;
          1|2|3|6)   return 0 ;;
                     # unknown codes we just say "not checked"
     esac
     if [[ "$response" =~ "404 Not Found" ]]; then
          return 1
     elif [[ "$response" =~ "200 OK" ]]; then
          return 2
     else
          return 0
     fi
}

check_revocation_crl() {
     local crl="$1"
     local jsonID="$2"
     local tmpfile=""
     local scheme retcode
     local -i success

     "$PHONE_OUT" || return 0
     [[ -n "$GOOD_CA_BUNDLE" ]] || return 0
     scheme="$(tolower "${crl%%://*}")"
     # The code for obtaining CRLs only supports LDAP, HTTP, and HTTPS URLs.
     [[ "$scheme" == http ]] || [[ "$scheme" == https ]] || [[ "$scheme" == ldap ]] || return 0
     tmpfile=$TEMPDIR/${NODE}-${NODEIP}.${crl##*\/} || exit $ERR_FCREATE
     if [[ "$scheme" == ldap ]]; then
          ldap_get "$crl" "$tmpfile" "$jsonID"
          success=$?
     else
          http_get "$crl" "$tmpfile"
          success=$?
     fi
     if [[ $success -eq 2 ]]; then
          return 0
     elif [[ $success -ne 0 ]]; then
          out ", "
          pr_warning "retrieval of \"$crl\" failed"
          fileout "$jsonID" "WARN" "CRL retrieval from $crl failed"
          return 1
     fi
     # -crl_download could be more elegant but is supported from 1.0.2 onwards only
     $OPENSSL crl -inform DER -in "$tmpfile" -outform PEM -out "${tmpfile%%.crl}.pem" &>$ERRFILE
     if [[ $? -ne 0 ]]; then
          pr_warning "conversion of \"$tmpfile\" failed"
          fileout "$jsonID" "WARN" "conversion of CRL to PEM format failed"
          return 1
     fi
     if grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TEMPDIR/intermediatecerts.pem; then
          $OPENSSL verify -crl_check -CAfile <(cat $ADDTL_CA_FILES "$GOOD_CA_BUNDLE" "${tmpfile%%.crl}.pem") -untrusted $TEMPDIR/intermediatecerts.pem $HOSTCERT &> "${tmpfile%%.crl}.err"
     else
          $OPENSSL verify -crl_check -CAfile <(cat $ADDTL_CA_FILES "$GOOD_CA_BUNDLE" "${tmpfile%%.crl}.pem") $HOSTCERT &> "${tmpfile%%.crl}.err"
     fi
     if [[ $? -eq 0 ]]; then
          out ", "
          pr_svrty_good "not revoked"
          fileout "$jsonID" "OK" "not revoked"
     else
          retcode=$(awk '/error [1-9][0-9]? at [0-9]+ depth lookup:/ { if (!found) {print $2; found=1} }' "${tmpfile%%.crl}.err")
          if [[ "$retcode" == 23 ]]; then # see verify_retcode_helper()
               out ", "
               pr_svrty_critical "revoked"
               fileout "$jsonID" "CRITICAL" "revoked"
               set_grade_cap "T" "Certificate revoked"
          else
               retcode="$(verify_retcode_helper "$retcode")"
               out " $retcode"
               retcode="${retcode#(}"
               retcode="${retcode%)}"
               fileout "$jsonID" "WARN" "$retcode"
               set_grade_cap "T" "Issues with certificate $retcode"
               if [[ $DEBUG -ge 2 ]]; then
                    outln
                    cat "${tmpfile%%.crl}.err"
               fi
          fi
     fi
     return 0
}

check_revocation_ocsp() {
     local uri="$1"
     local stapled_response="$2"
     local jsonID="$3"
     local tmpfile=""
     local -i success
     local response=""
     local host_header=""

     "$PHONE_OUT" || [[ -n "$stapled_response" ]] || return 0
     [[ -n "$GOOD_CA_BUNDLE" ]] || return 0
     if [[ -n "$PROXY" ]] && ! "$IGN_OCSP_PROXY"; then
          # see #1106 and https://github.com/openssl/openssl/issues/6965
          out ", "
          pr_warning "revocation not tested as \"openssl ocsp\" doesn't support a proxy"
          fileout "$jsonID" "WARN" "Revocation not tested as openssl ocsp doesn't support a proxy"
          return 0
     fi
     grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TEMPDIR/intermediatecerts.pem || return 0
     tmpfile=$TEMPDIR/${NODE}-${NODEIP}.${uri##*\/} || exit $ERR_FCREATE
     if [[ -n "$stapled_response" ]]; then
          asciihex_to_binary "$stapled_response" > "$TEMPDIR/stapled_ocsp_response.dd"
          $OPENSSL ocsp -no_nonce -respin "$TEMPDIR/stapled_ocsp_response.dd" \
               -issuer $TEMPDIR/hostcert_issuer.pem -verify_other $TEMPDIR/intermediatecerts.pem \
               -CAfile <(cat $ADDTL_CA_FILES "$GOOD_CA_BUNDLE") -cert $HOSTCERT -text &> "$tmpfile"
     else
          host_header=${uri##http://}
          host_header=${host_header%%/*}
          if [[ "$OSSL_NAME" =~ LibreSSL ]]; then
               host_header="-header Host ${host_header}"
          elif [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 1.1.0* ]] || [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 1.1.1* ]] || \
               [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 3.0.0* ]]; then
               host_header="-header Host=${host_header}"
          else
               host_header="-header Host ${host_header}"
          fi
          $OPENSSL ocsp -no_nonce ${host_header} -url "$uri" \
               -issuer $TEMPDIR/hostcert_issuer.pem -verify_other $TEMPDIR/intermediatecerts.pem \
               -CAfile <(cat $ADDTL_CA_FILES "$GOOD_CA_BUNDLE") -cert $HOSTCERT -text &> "$tmpfile"
     fi
     if [[ $? -eq 0 ]] && grep -Fq "Response verify OK" "$tmpfile"; then
          response="$(grep -F "$HOSTCERT: " "$tmpfile")"
          response="${response#$HOSTCERT: }"
          response="${response%\.}"
          if [[ "$response" =~ "good" ]]; then
               out ", "
               pr_svrty_good "not revoked"
               fileout "$jsonID" "OK" "not revoked"
          elif [[ "$response" =~ "revoked" ]]; then
               out ", "
               pr_svrty_critical "revoked"
               fileout "$jsonID" "CRITICAL" "revoked"
               set_grade_cap "T" "Certificate revoked"
          else
               out ", "
               pr_warning "error querying OCSP responder"
               fileout "$jsonID" "WARN" "$response"
               if [[ $DEBUG -ge 2 ]]; then
                    outln
                    cat "$tmpfile"
               else
                    out " ($response)"
               fi
          fi
     else
          [[ -s "$tmpfile" ]] || response="empty ocsp response"
          [[ -z "$response" ]] && response="$(awk '/Responder Error:/ { print $3 }' "$tmpfile")"
          [[ -z "$response" ]] && grep -Fq "Response Verify Failure" "$tmpfile" && response="unable to verify response"
          [[ -z "$response" ]] && response="$(awk -F':' '/Code/ { print $NF }' $tmpfile)"
          out ", "
          pr_warning "error querying OCSP responder"
          fileout "$jsonID" "WARN" "$response"
          if [[ $DEBUG -ge 2 ]]; then
               outln
               [[ -s "$tmpfile" ]] && cat "$tmpfile" || echo "empty ocsp response"
          elif [[ -n "$response" ]]; then
               out " ($response)"
          fi
     fi
}

wait_kill(){
     local pid=$1             # pid we wait for or kill
     local maxsleep=$2        # how long we wait before killing

     HAD_SLEPT=0
     while true; do
          if ! ps $pid >/dev/null ; then
               return 0       # process terminated before didn't reach $maxsleep
          fi
          [[ "$DEBUG" -ge 6 ]] && ps $pid
          sleep 1
          maxsleep=$((maxsleep - 1))
          HAD_SLEPT=$((HAD_SLEPT + 1))
          test $maxsleep -le 0 && break
     done                     # needs to be killed:
     kill $pid >&2 2>/dev/null
     wait $pid 2>/dev/null    # make sure pid terminated, see wait(1p)
     return 3                 # means killed
}

# parse_date date format input-format
if "$HAS_GNUDATE"; then            # Linux and NetBSD
     parse_date() {
          LC_ALL=C date -d "$1" "$2"
     }
elif "$HAS_FREEBSDDATE"; then      # FreeBSD, OS X and newer (~6.6) OpenBSD versions
     parse_date() {
          LC_ALL=C TZ=GMT date -j -f "$3" "$2" "$1"
     }
elif "$HAS_OPENBSDDATE"; then
# We bascially echo it as a conversion as we want it is too difficult. Approach for that would be:
#  printf '%s\n' "$1" | awk '{ printf "%04d%02d%02d\n", $4, $2, (index("JanFebMarAprMayJunJulAugSepOctNovDec",$1)+2)/3}'
# 4: year, 1: month, 2: day, $3: time  (e.g. "Dec 8 10:16:13 2016")
# This way we could also kind of convert args to epoch but as newer OpenBSDs "date" behave like FreeBSD
     parse_date() {
          local tmp=""
          if [[ $2 == +%s* ]]; then
               echo "${1// GMT}"
          else
               tmp="$(printf '%s\n' "$1" | awk '{ printf "%04d-%02d-%02d %08s\n", $4, (index("JanFebMarAprMayJunJulAugSepOctNovDec",$1)+2)/3, $2, $3 }')"
               echo "${tmp%:*}"         # remove seconds, result now is in line with GNU date 2016-12-08 10:16
          fi
     }
else
     parse_date() {
          LC_ALL=C date -j "$2" "$1"
     }
fi

# arg1: An ASCII-HEX string
# Print $arg1 in binary format
asciihex_to_binary() {
     local string="$1"
     local -i len
     local -i i ip2 ip4 ip6 ip8 ip10 ip12 ip14
     local -i remainder

     len=${#string}
     [[ $len%2 -ne 0 ]] && return 1

     for (( i=0; i <= len-16 ; i+=16 )); do
          ip2=$((i+2)); ip4=$((i+4)); ip6=$((i+6)); ip8=$((i+8)); ip10=$((i+10)); ip12=$((i+12)); ip14=$((i+14))
          printf -- "\x${string:i:2}\x${string:ip2:2}\x${string:ip4:2}\x${string:ip6:2}\x${string:ip8:2}\x${string:ip10:2}\x${string:ip12:2}\x${string:ip14:2}"
     done

     ip2=$((i+2)); ip4=$((i+4)); ip6=$((i+6)); ip8=$((i+8)); ip10=$((i+10)); ip12=$((i+12)); ip14=$((i+14))
     remainder=$len-$i
     case $remainder in
           2) printf -- "\x${string:i:2}" ;;
           4) printf -- "\x${string:i:2}\x${string:ip2:2}" ;;
           6) printf -- "\x${string:i:2}\x${string:ip2:2}\x${string:ip4:2}" ;;
           8) printf -- "\x${string:i:2}\x${string:ip2:2}\x${string:ip4:2}\x${string:ip6:2}" ;;
          10) printf -- "\x${string:i:2}\x${string:ip2:2}\x${string:ip4:2}\x${string:ip6:2}\x${string:ip8:2}" ;;
          12) printf -- "\x${string:i:2}\x${string:ip2:2}\x${string:ip4:2}\x${string:ip6:2}\x${string:ip8:2}\x${string:ip10:2}" ;;
          14) printf -- "\x${string:i:2}\x${string:ip2:2}\x${string:ip4:2}\x${string:ip6:2}\x${string:ip8:2}\x${string:ip10:2}\x${string:ip12:2}" ;;
     esac
     return 0
}

# arg1: text string
# Output a comma-separated ASCII-HEX string representation of the input string.
string_to_asciihex() {
     local string="$1"
     local -i i eos
     local output=""

     eos=${#string}-1
     for (( i=0; i<eos; i++ )); do
          output+="$(printf "%02x," "'${string:i:1}")"
     done
     [[ -n "$string" ]] && output+="$(printf "%02x" "'${string:eos:1}")"
     tm_out "$output"
     return 0

}

# Adjust options to $OPENSSL s_client based on OpenSSL version and protocol version
s_client_options() {
     local options=" $1"
     local ciphers="notpresent" tls13_ciphers="notpresent"

     # Extract the TLSv1.3 ciphers and the non-TLSv1.3 ciphers
     if [[ " $options " =~ " -cipher " ]]; then
          ciphers="${options#* -cipher }"
          ciphers="${ciphers%% *}"
          options="${options//-cipher $ciphers/}"
          ciphers="${ciphers##\'}"
          ciphers="${ciphers%%\'}"
     fi
     if [[ " $options " =~ " -ciphersuites " ]]; then
          tls13_ciphers="${options#* -ciphersuites }"
          tls13_ciphers="${tls13_ciphers%% *}"
          options="${options//-ciphersuites $tls13_ciphers/}"
          tls13_ciphers="${tls13_ciphers##\'}"
          tls13_ciphers="${tls13_ciphers%%\'}"
          [[ "$tls13_ciphers" == ALL ]] && tls13_ciphers="$TLS13_OSSL_CIPHERS"
     fi

     # Don't include the -servername option for an SSLv2 or SSLv3 ClientHello.
     [[ -n "$SNI" ]] && [[ " $options " =~ \ -ssl[2|3]\  ]] && options="${options//$SNI/}"

     # The server_name extension should not be included in the ClientHello unless
     # the -servername option is provided. However, OpenSSL 1.1.1 will include the
     # server_name extension unless the -noservername option is provided. So, if
     # the command line doesn't include -servername and the -noservername option is
     # supported, then add -noservername to the options.
     "$HAS_NOSERVERNAME" && [[ ! " $options " =~ " -servername " ]] && options+=" -noservername"

     # Newer versions of OpenSSL have dropped support for the -no_ssl2 option, so
     # remove any -no_ssl2 option if the option isn't supported. (Since versions of
     # OpenSSL that don't support -no_ssl2 also don't support SSLv2, the option
     # isn't needed for these versions of OpenSSL.)
     ! "$HAS_NO_SSL2" && options="${options//-no_ssl2/}"

     # At least one server will fail under some circumstances if compression methods are offered.
     # So, only offer compression methods if necessary for the test. In OpenSSL 1.1.0 and
     # 1.1.1 compression is only offered if the "-comp" option is provided.
     # OpenSSL 1.0.0, 1.0.1, and 1.0.2 offer compression unless the "-no_comp" option is provided.
     # OpenSSL 0.9.8 does not support either the "-comp" or the "-no_comp" option.
     if [[ " $options " =~ " -comp " ]]; then
          # Compression is needed for the test. So, remove "-comp" if it isn't supported, but
          # otherwise make no changes.
          ! "$HAS_COMP" && options="${options//-comp/}"
     else
          # Compression is not needed. So, specify "-no_comp" if that option is supported.
          "$HAS_NO_COMP" && options+=" -no_comp"
     fi

     # If $OPENSSL is compiled with TLSv1.3 support and s_client is called without
     # specifying a protocol, but specifying a list of ciphers that doesn't include
     # any TLSv1.3 ciphers, then the command will always fail. So, if $OPENSSL supports
     # TLSv1.3 and a cipher list is provided, but no protocol is specified, then add
     # -no_tls1_3 if no TLSv1.3 ciphers are provided.
     if "$HAS_TLS13" && [[ "$ciphers" != notpresent ]] && \
          ( [[ "$tls13_ciphers" == notpresent ]] || [[ -z "$tls13_ciphers" ]] ) && \
          [[ ! " $options " =~ \ -ssl[2|3]\  ]] && \
          [[ ! " $options " =~ \ -tls1\  ]] && \
          [[ ! " $options " =~ \ -tls1_[1|2|3]\  ]]; then
          options+=" -no_tls1_3"
     fi

     if "$HAS_SECLEVEL"; then
          if [[ "$ciphers" == notpresent ]]; then
               [[ ! " $options " =~ \ -tls1_3\  ]] && ciphers="@SECLEVEL=0:ALL:COMPLEMENTOFALL"
          else
               ciphers="@SECLEVEL=0:$ciphers"
          fi
     fi
     if [[ "$ciphers" != notpresent ]] || [[ "$tls13_ciphers" != notpresent ]]; then
          if ! "$HAS_CIPHERSUITES"; then
               [[ "$ciphers" == notpresent ]] && ciphers=""
               [[ "$tls13_ciphers" == notpresent ]] && tls13_ciphers=""
               [[ -n "$ciphers" ]] && [[ -n "$tls13_ciphers" ]] && ciphers=":$ciphers"
               ciphers="$tls13_ciphers$ciphers"
               options+=" -cipher $ciphers"
          else
               if [[ "$ciphers" != notpresent ]] && [[ -n "$ciphers" ]]; then
                    options+=" -cipher $ciphers"
               fi
               if [[ "$tls13_ciphers" != notpresent ]] && [[ -n "$tls13_ciphers" ]]; then
                    options+=" -ciphersuites $tls13_ciphers"
               fi
          fi
     fi

     # OpenSSL's name for secp256r1 is prime256v1. So whenever we encounter this
     # (e.g. client simulations) we replace it with the name which OpenSSL understands
     # This shouldn't be needed. We have this here as a last resort
     if [[ "$1" =~ " -curves " ]]; then
          ! "$HAS_CURVES" && options="${options// -curves / -groups }"
          [[ "$1" =~ secp192r1 ]] && options="${options//secp192r1/prime192v1}"
          [[ "$1" =~ secp256r1 ]] && options="${options//secp256r1/prime256v1}"
     fi
     tm_out "$options $keyopts"
}

###### check code starts here ######

# determines whether the port has an HTTP service running or not (plain TLS, no STARTTLS)
# arg1 could be the protocol determined as "working". IIS6 needs that
service_detection() {
     local -i was_killed

     if ! "$CLIENT_AUTH"; then
          if ! "$HAS_TLS13" && "$TLS13_ONLY"; then
               # Using sockets is a lot slower than using OpenSSL, and it is
               # not as reliable, but if OpenSSL can't connect to the server,
               # trying with sockets is better than not even trying.
               tls_sockets "04" "$TLS13_CIPHER" "all+" "" "" false
               if [[ $? -eq 0 ]]; then
                    plaintext="$(tm_out "$GET_REQ11" | hexdump -v -e '16/1 "%02X"')"
                    plaintext="${plaintext%%[!0-9A-F]*}"
                    send_app_data "$plaintext"
                    if [[ $? -eq 0 ]]; then
                         receive_app_data true
                         [[ $? -eq 0 ]] || > "$TMPFILE"
                    else
                         > "$TMPFILE"
                    fi
                    send_close_notify "$DETECTED_TLS_VERSION"
               else
                    > "$TMPFILE"
               fi
          else
               # SNI is not standardized for !HTTPS but fortunately for other protocols s_client doesn't seem to care
               tm_out "$GET_REQ11" | $OPENSSL s_client $(s_client_options "$1 -quiet $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE &
               wait_kill $! $HEADER_MAXSLEEP
               was_killed=$?
          fi
          head $TMPFILE | grep -aq '^HTTP\/' && SERVICE=HTTP
          [[ -z "$SERVICE" ]] && head $TMPFILE | grep -waq "SMTP|ESMTP|Exim|IdeaSmtpServer|Kerio Connect|Postfix" && SERVICE=SMTP   # I know some overlap here
          [[ -z "$SERVICE" ]] && head $TMPFILE | grep -Ewaq "POP|Gpop|MailEnable POP3 Server|OK Dovecot|Cyrus POP3" && SERVICE=POP  # I know some overlap here
          [[ -z "$SERVICE" ]] && head $TMPFILE | grep -Ewaq "IMAP|IMAP4|Cyrus IMAP4IMAP4rev1|IMAP4REV1|Gimap" && SERVICE=IMAP       # I know some overlap here
          [[ -z "$SERVICE" ]] && head $TMPFILE | grep -aq FTP && SERVICE=FTP
          [[ -z "$SERVICE" ]] && head $TMPFILE | grep -Eaqi "jabber|xmpp" && SERVICE=XMPP
          [[ -z "$SERVICE" ]] && head $TMPFILE | grep -Eaqw "Jive News|InterNetNews|NNRP|INN|Kerio Connect|NNTP Service|Kerio MailServer|NNTP server" && SERVICE=NNTP
          # MongoDB port 27017 will respond to a GET request with a mocked HTTP response
          [[ "$SERVICE" == HTTP ]] && head $TMPFILE | grep -Eaqw "MongoDB" && SERVICE=MongoDB
          debugme head -50 $TMPFILE | sed -e '/<HTML>/,$d' -e '/<html>/,$d' -e '/<XML/,$d' -e '/<xml/,$d' -e '/<\?XML/,$d' -e '/<\?xml/,$d' -e '/<\!DOCTYPE/,$d' -e '/<\!doctype/,$d'
     fi

     out " Service detected:      $CORRECT_SPACES"
     jsonID="service"
     case $SERVICE in
          HTTP)
               out " $SERVICE"
               fileout "${jsonID}" "INFO" "$SERVICE"
               ;;
          IMAP|POP|SMTP|NNTP|MongoDB)
               out " $SERVICE, thus skipping HTTP specific checks"
               fileout "${jsonID}" "INFO" "$SERVICE, thus skipping HTTP specific checks"
               ;;
          *)   if "$CLIENT_AUTH"; then
                    out " certificate-based authentication => skipping all HTTP checks"
                    echo "certificate-based authentication => skipping all HTTP checks" >$TMPFILE
                    fileout "${jsonID}" "INFO" "certificate-based authentication => skipping all HTTP checks"
               else
                    out " Couldn't determine what's running on port $PORT"
                    if "$ASSUME_HTTP"; then
                         SERVICE=HTTP
                         out " -- ASSUME_HTTP set though"
                         fileout "${jsonID}" "DEBUG" "Couldn't determine service -- ASSUME_HTTP set"
                    else
                         out ", assuming no HTTP service => skipping all HTTP checks"
                         fileout "${jsonID}" "DEBUG" "Couldn't determine service, skipping all HTTP checks"
                    fi
               fi
               ;;
     esac

     outln "\n"
     tmpfile_handle ${FUNCNAME[0]}.txt
     return 0
}

# 1: counter variable
# 2: threshold for this variable
# 3: string for first occurrence of problem
# 4: string for repeated occurrence of problem
#
connectivity_problem() {
     if [[ $1 -lt $2 ]]; then
          if "$TLS13_ONLY" && ! "$HAS_TLS13"; then
               :
          else
               prln_warning " Oops: $3"
          fi
          return 0
     fi
     if [[ $1 -ge $2 ]]; then
          if [[ "$4" =~ openssl\ s_client\ connect ]] ; then
               fatal "$4" $ERR_CONNECT "Consider increasing MAX_OSSL_FAIL (currently: $2)"
          elif [[ "$4" =~ repeated\ TCP\ connect ]]; then
               fatal "$4" $ERR_CONNECT "Consider increasing MAX_SOCKET_FAIL (currently: $2)"
          fi
          fatal "$4" $ERR_CONNECT
     fi
}


#problems not handled: chunked
run_http_header() {
     local header
     local referer useragent
     local url redirect
     local jsonID="HTTP_status_code"
     local spaces="                            "

     HEADERFILE=$TEMPDIR/$NODEIP.http_header.txt
     if [[ $NR_HEADER_FAIL -eq 0 ]]; then
          # skip repeating this line if it's 2nd, 3rd,.. try
          outln; pr_headlineln " Testing HTTP header response @ \"$URL_PATH\" "
          outln
     fi
     if [[ $NR_HEADER_FAIL -ge $MAX_HEADER_FAIL ]]; then
          # signal to caller we have a problem
          return 1
     fi

     pr_bold " HTTP Status Code           "
     [[ -z "$1" ]] && url="/" || url="$1"
     tm_out "$GET_REQ11" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") >$HEADERFILE 2>$ERRFILE &
     wait_kill $! $HEADER_MAXSLEEP
     if [[ $? -eq 0 ]]; then
          # Issue HTTP GET again as it properly finished within $HEADER_MAXSLEEP and didn't hang.
          # Doing it again in the foreground to get an accurate header time
          tm_out "$GET_REQ11" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") >$HEADERFILE 2>$ERRFILE
          NOW_TIME=$(date "+%s")
          HTTP_TIME=$(awk -F': ' '/^date:/ { print $2 }  /^Date:/ { print $2 }' $HEADERFILE)
          HAD_SLEPT=0
     else
          # 1st GET request hung and needed to be killed. Check whether it succeeded anyway:
          if grep -Eiaq "XML|HTML|DOCTYPE|HTTP|Connection" $HEADERFILE; then
               # correct by seconds we slept, HAD_SLEPT comes from wait_kill()
               NOW_TIME=$(($(date "+%s") - HAD_SLEPT))
               HTTP_TIME=$(awk -F': ' '/^date:/ { print $2 }  /^Date:/ { print $2 }' $HEADERFILE)
          else
               prln_warning " likely HTTP header requests failed (#lines: $(wc -l $HEADERFILE | awk '{ print $1 }'))"
               [[ "$DEBUG" -lt 1 ]] && outln "Rerun with DEBUG>=1 and inspect $HEADERFILE\n"
               fileout "HTTP_status_code" "WARN" "HTTP header request failed"
               debugme cat $HEADERFILE
               ((NR_HEADER_FAIL++))
          fi
     fi
     if [[ ! -s $HEADERFILE ]]; then
          ((NR_HEADER_FAIL++))
          if [[ $NR_HEADER_FAIL -ge $MAX_HEADER_FAIL ]]; then
               # Now, try to give a hint whether it would make sense to try with OpenSSL 1.1.0 or 1.1.1 instead
               if [[ $CURVES_OFFERED == X448 ]] && ! "$HAS_X448" ; then
                    generic_nonfatal "HTTP header was repeatedly zero due to missing X448 curve." "${spaces}OpenSSL 1.1.1 might help. Skipping complete HTTP header section."
               elif [[ $CURVES_OFFERED == X25519 ]] && ! "$HAS_X25519" ; then
                    generic_nonfatal "HTTP header was repeatedly zero due to missing X25519 curve." "${spaces}OpenSSL 1.1.0 might help. Skipping complete HTTP header section."
               elif [[ $CURVES_OFFERED =~ X25519 ]] && [[ $CURVES_OFFERED =~ X448 ]] && ! "$HAS_X25519" && ! "$HAS_X448"; then
                    generic_nonfatal "HTTP header was repeatedly zero due to missing X25519/X448 curves." "${spaces}OpenSSL >=1.1.0 might help. Skipping complete HTTP header section."
               else
                    # we could give more hints but these are the most likely cases
                    generic_nonfatal "HTTP header was repeatedly zero." "Skipping complete HTTP header section."
               fi
               KNOWN_OSSL_PROB=true
               return 1
          else
               pr_warning "HTTP header reply empty. "
               fileout "$jsonID" "WARN" "HTTP header reply empty"
          fi
     fi

     # Populate vars for HTTP time
     debugme echo "NOW_TIME: $NOW_TIME | HTTP_TIME: $HTTP_TIME"

     # Quit on first empty line to catch 98% of the cases. Next pattern is there because the SEDs tested
     # so far seem not to be fine with header containing x0d x0a (CRLF) which is the usal case.
     # So we also trigger also on any sign on a single line which is not alphanumeric (plus _)
     sed -e '/^$/q' -e '/^[^a-zA-Z_0-9]$/q' $HEADERFILE >$HEADERFILE.tmp
     # Now to be more sure we delete from '<' or '{' maybe with a leading blank until the end
     sed -e '/^ *<.*$/d' -e '/^ *{.*$/d'  $HEADERFILE.tmp >$HEADERFILE
     debugme echo -e "---\n $(< $HEADERFILE) \n---"

     HTTP_STATUS_CODE=$(awk '/^HTTP\// { print $2 }' $HEADERFILE 2>>$ERRFILE)
     msg_thereafter=$(awk -F"$HTTP_STATUS_CODE" '/^HTTP\// { print $2 }' $HEADERFILE 2>>$ERRFILE)   # dirty trick to use the status code as a
     msg_thereafter=$(strip_lf "$msg_thereafter")                                                   # field separator, otherwise we need a loop with awk
     debugme echo "Status/MSG: $HTTP_STATUS_CODE $msg_thereafter"

     [[ -n "$HTTP_STATUS_CODE" ]] && out "  $HTTP_STATUS_CODE$msg_thereafter"
     case $HTTP_STATUS_CODE in
          301|302|307|308)
               redirect=$(grep -a '^Location' $HEADERFILE | sed 's/Location: //' | tr -d '\r\n')
               out ", redirecting to \""; pr_url "$redirect"; out "\""
               if [[ $redirect =~ http:// ]]; then
                    pr_svrty_high " -- Redirect to insecure URL (NOT ok)"
                    fileout "insecure_redirect" "HIGH" "Redirect to insecure URL: \"$redirect\""
               fi
               fileout "$jsonID" "INFO" "$HTTP_STATUS_CODE$msg_thereafter (\"$URL_PATH\")"
               ;;
          200|204|403|405)
               fileout "$jsonID" "INFO" "$HTTP_STATUS_CODE$msg_thereafter (\"$URL_PATH\")"
               ;;
          206)
               out " -- WHAT?"
               fileout "$jsonID" "INFO" "$HTTP_STATUS_CODE$msg_thereafter (\"$URL_PATH\") -- WHAT?"
               # partial content shouldn't happen
               ;;
          400)
               pr_cyan " (Hint: better try another URL)"
               fileout "$jsonID" "INFO" "$HTTP_STATUS_CODE$msg_thereafter (\"$URL_PATH\") -- better try another URL"
               ;;
          401)
               grep -aq "^WWW-Authenticate" $HEADERFILE && out "  "; out "$(strip_lf "$(grep -a "^WWW-Authenticate" $HEADERFILE)")"
               fileout "$jsonID" "INFO" "$HTTP_STATUS_CODE$msg_thereafter (\"$URL_PATH\") -- $(grep -a "^WWW-Authenticate" $HEADERFILE)"
               ;;
          404)
               out " (Hint: supply a path which doesn't give a \"$HTTP_STATUS_CODE$msg_thereafter\")"
               fileout "$jsonID" "INFO" "$HTTP_STATUS_CODE$msg_thereafter (\"$URL_PATH\")"
               ;;
          "")
               prln_warning "No HTTP status code."
               fileout "$jsonID" "WARN" "No HTTP status code"
               return 1
               ;;
          *)
               pr_warning ". Oh, didn't expect \"$HTTP_STATUS_CODE$msg_thereafter\""
               fileout "$jsonID" "WARN" "Unexpected $HTTP_STATUS_CODE$msg_thereafter @ \"$URL_PATH\""
               ;;
     esac
     outln

     # we don't call "tmpfile_handle ${FUNCNAME[0]}.txt" as we need the header file in other functions!
     return 0
}

# Borrowed from Glenn Jackman, see https://unix.stackexchange.com/users/4667/glenn-jackman
#
match_ipv4_httpheader() {
     local octet="(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])"
     local ipv4address="$octet\\.$octet\\.$octet\\.$octet"
     local whitelisted_header="pagespeed|page-speed|^Content-Security-Policy|^MicrosoftSharePointTeamServices|^X-OWA-Version|^Location|^Server: PRTG"
     local your_ip_msg="(check if it's your IP address or e.g. a cluster IP)"
     local result
     local first=true
     local spaces="                              "
     local count
     local jsonID="ipv4_in_header"
     local cwe="CWE-212"
     local cve=""

     if [[ ! -s $HEADERFILE ]]; then
          run_http_header "$1" || return 1
     fi

     # Whitelist some headers as they are mistakenly identified as ipv4 address. Issues #158, #323.
     # Also facebook used to have a CSP rule for 127.0.0.1
     if grep -Evai "$whitelisted_header" $HEADERFILE | grep -Eiq "$ipv4address"; then
          pr_bold " IPv4 address in header       "
          count=0
          while read line; do
               result="$(grep -E "$ipv4address" <<< "$line")"
               result=$(strip_lf "$result")
               if [[ -n "$result" ]]; then
                    if ! $first; then
                         out "$spaces"
                         your_ip_msg=""
                    else
                         first=false
                    fi
                    pr_svrty_medium "$result"
                    outln "\n$spaces$your_ip_msg"
                    fileout "$jsonID" "MEDIUM" "$result $your_ip_msg" "$cve" "$cwe"
               fi
               count=$count+1
          done < $HEADERFILE
     fi
}


run_http_date() {
     local difftime
     local spaces="                              "
     jsonID="HTTP_clock_skew"

     if [[ $SERVICE != HTTP ]] || "$CLIENT_AUTH"; then
          return 0
     fi
     if [[ ! -s $HEADERFILE ]]; then
          run_http_header "$1" || return 1
     fi
     pr_bold " HTTP clock skew              "
     if [[ -n "$HTTP_TIME" ]]; then
          HTTP_TIME="$(strip_lf "$HTTP_TIME")"
          if "$HAS_OPENBSDDATE"; then
               # We won't normalize the date under an OpenBSD thus no subtraction is feasible
               outln "remote: $HTTP_TIME"
               out "${spaces}local:  $(LC_ALL=C TZ=GMT date "+%a, %d %b %Y %T %Z")"
               fileout "$jsonID" "INFO" "$HTTP_TIME - $(TZ=GMT date "+%a, %d %b %Y %T %Z")"
          else
               HTTP_TIME="$(parse_date "$HTTP_TIME" "+%s" "%a, %d %b %Y %T %Z" 2>>$ERRFILE)"
               difftime=$((HTTP_TIME - NOW_TIME))
               [[ $difftime != "-"* ]] && [[ $difftime != "0" ]] && difftime="+$difftime"
               # process was killed, so we need to add an error
               [[ $HAD_SLEPT -ne 0 ]] && difftime="$difftime (± 1.5)"
               out "$difftime sec from localtime";
               fileout "$jsonID" "INFO" "$difftime seconds from localtime"
          fi
     else
          out "Got no HTTP time, maybe try different URL?";
          fileout "$jsonID" "INFO" "Got no HTTP time, maybe try different URL?"
     fi
     debugme tm_out ", HTTP_TIME in epoch: $HTTP_TIME"
     outln
     match_ipv4_httpheader "$1"
     return 0
}


# HEADERFILE needs to contain the HTTP header (made sure by invoker)
# arg1: key=word to match
# arg2: hint for fileout() if double header
# arg3: indentation, i.e string w spaces
# arg4: whether we need a CR before "misconfiguration"
# returns:
#    0 if header not found
#    1-n nr of headers found, then in HEADERVALUE the first value from key
#
match_httpheader_key() {
     local key="$1"
     local spaces="$3"
     local first=$4
     local -i nr=0

     nr=$(grep -Eaic "^ *$key:" $HEADERFILE)
     if [[ $nr -eq 0 ]]; then
          HEADERVALUE=""
          return 0
     elif [[ $nr -eq 1 ]]; then
          HEADERVALUE="$(grep -Eia "^ *$key:" $HEADERFILE)"
          HEADERVALUE="${HEADERVALUE#*:}"                        # remove leading part=key to colon
          HEADERVALUE="$(strip_lf "$HEADERVALUE")"
          HEADERVALUE="$(strip_leading_space "$HEADERVALUE")"
          "$first" || out "$spaces"
          return 1
     else
          "$first" || out "$spaces"
          pr_svrty_medium "misconfiguration: "
          pr_italic "$key"
          pr_svrty_medium " ${nr}x"
          outln " -- checking first one only"
          out "$spaces"
          HEADERVALUE="$(grep -Fai "$key:" $HEADERFILE | head -1)"
          HEADERVALUE="${HEADERVALUE#*:}"
          HEADERVALUE="$(strip_lf "$HEADERVALUE")"
          HEADERVALUE="$(strip_leading_space "$HEADERVALUE")"
          [[ $DEBUG -ge 2 ]] && tm_italic "$HEADERVALUE" && tm_out "\n$spaces"
          fileout "${2}_multiple" "MEDIUM" "Multiple $2 headers. Using first header: $HEADERVALUE"
          return $nr
     fi
}

includeSubDomains() {
     if grep -aiqw includeSubDomains "$1"; then
          pr_svrty_good ", includeSubDomains"
          return 0
     else
          pr_litecyan ", just this domain"
          return 1
     fi
}

preload() {
     if grep -aiqw preload "$1"; then
          pr_svrty_good ", preload"
          return 0
     else
          return 1
     fi
}


run_hsts() {
     local hsts_age_sec
     local hsts_age_days
     local spaces="                              "
     local jsonID="HSTS"

     if [[ ! -s $HEADERFILE ]]; then
          run_http_header "$1" || return 1
     fi
     pr_bold " Strict Transport Security    "
     match_httpheader_key "Strict-Transport-Security" "HSTS" "$spaces" "true"
     if [[ $? -ne 0 ]]; then
          echo "$HEADERVALUE" >$TMPFILE
          hsts_age_sec=$(sed -e 's/[^0-9]*//g' <<< $HEADERVALUE)
          debugme echo "hsts_age_sec: $hsts_age_sec"
          if [[ -n $hsts_age_sec ]]; then
               hsts_age_days=$(( hsts_age_sec / 86400))
          else
               hsts_age_days=-1
          fi
          if [[ $hsts_age_days -eq -1 ]]; then
               pr_svrty_medium "misconfiguration: HSTS max-age (recommended > $HSTS_MIN seconds = $((HSTS_MIN/86400)) days ) is required but missing"
               fileout "${jsonID}_time" "MEDIUM" "misconfiguration, parameter max-age (recommended > $HSTS_MIN seconds = $((HSTS_MIN/86400)) days) missing"
               set_grade_cap "A" "HSTS max-age is misconfigured"
          elif [[ $hsts_age_sec -eq 0 ]]; then
               pr_svrty_low "HSTS max-age is set to 0. HSTS is disabled"
               fileout "${jsonID}_time" "LOW" "0. HSTS is disabled"
               set_grade_cap "A" "HSTS is disabled"
          elif [[ $hsts_age_sec -gt $HSTS_MIN ]]; then
               pr_svrty_good "$hsts_age_days days" ; out "=$hsts_age_sec s"
               fileout "${jsonID}_time" "OK" "$hsts_age_days days (=$hsts_age_sec seconds) > $HSTS_MIN seconds"
          else
               pr_svrty_medium "$hsts_age_sec s = $hsts_age_days days is too short ( > $HSTS_MIN seconds recommended)"
               fileout "${jsonID}_time" "MEDIUM" "max-age too short. $hsts_age_days days (=$hsts_age_sec seconds) <= $HSTS_MIN seconds"
               set_grade_cap "A" "HSTS max-age is too short"
          fi
          if includeSubDomains "$TMPFILE"; then
               fileout "${jsonID}_subdomains" "OK" "includes subdomains"
          else
               fileout "${jsonID}_subdomains" "INFO" "only for this domain"
          fi
          if preload "$TMPFILE"; then
               fileout "${jsonID}_preload" "OK" "domain IS marked for preloading"
          else
               fileout "${jsonID}_preload" "INFO" "domain is NOT marked for preloading"
               #FIXME: To be checked against preloading lists,
               # e.g. https://dxr.mozilla.org/mozilla-central/source/security/manager/boot/src/nsSTSPreloadList.inc
               #      https://chromium.googlesource.com/chromium/src/+/master/net/http/transport_security_state_static.json
          fi
     else
          pr_svrty_low "not offered"
          fileout "$jsonID" "LOW" "HSTS is not offered.\nHTTP Strict Transport Security (also named HSTS) is an opt-in security enhancement that is specified by a web application through the use of a special response header. Once a supported browser receives this header that browser will prevent any communications from being sent over HTTP to the specified domain and will instead send all communications over HTTPS. It also prevents HTTPS click through prompts on browsers.\nRecommendation:\nIf the site owner would like their domain to be included in the HSTS preload list maintained by Chrome (and used by Firefox and Safari), then use the header below.\nSending the preload directive from your site can have PERMANENT CONSEQUENCES and prevent users from accessing your site and any of its subdomains if you find you need to switch back to HTTP.\nStrict-Transport-Security: max-age=31536000; includeSubDomains; preload\nRef: https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Strict_Transport_Security_Cheat_Sheet.html"
          set_grade_cap "A" "HSTS is not offered"
     fi
     outln

     tmpfile_handle ${FUNCNAME[0]}.txt
     return 0
}


run_hpkp() {
     local -i hpkp_age_sec
     local -i hpkp_age_days
     local -i hpkp_nr_keys
     local hpkp_spki hpkp_spki_hostcert
     local -a backup_spki
     local spaces="                             "
     local spaces_indented="                  "
     local certificate_found=false
     local -i i nrsaved
     local first_hpkp_header
     local spki
     local ca_hashes="$TESTSSL_INSTALL_DIR/etc/ca_hashes.txt"

     if [[ ! -s $HEADERFILE ]]; then
          run_http_header "$1" || return 1
     fi
     pr_bold " Public Key Pinning           "
     grep -aiw '^Public-Key-Pins' $HEADERFILE >$TMPFILE                    # TMPFILE includes report-only
     if [[ $? -eq 0 ]]; then
          if [[ $(grep -aci '^Public-Key-Pins:' $TMPFILE) -gt 1 ]]; then
               pr_svrty_medium "Misconfiguration, multiple Public-Key-Pins headers"
               outln ", taking first line"
               fileout "HPKP_error" "MEDIUM" "multiple Public-Key-Pins in header"
               first_hpkp_header="$(grep -ai '^Public-Key-Pins:' $TMPFILE | head -1)"
               # we only evaluate the keys here, unless they a not present
               out "$spaces "
               set_grade_cap "A" "Problems with HTTP Public Key Pinning (HPKP)"
          elif [[ $(grep -aci '^Public-Key-Pins-Report-Only:' $TMPFILE) -gt 1 ]]; then
               outln "Multiple HPKP headers (Report-Only), taking first line"
               fileout "HPKP_notice" "INFO" "multiple Public-Key-Pins-Report-Only in header"
               first_hpkp_header="$(grep -ai '^Public-Key-Pins-Report-Only:' $TMPFILE | head -1)"
               out "$spaces "
          elif [[ $(grep -Eaci '^Public-Key-Pins:|^Public-Key-Pins-Report-Only:' $TMPFILE) -eq 2 ]]; then
               outln "Public-Key-Pins + Public-Key-Pins-Report-Only detected. Continue with first one"
               first_hpkp_header="$(grep -ai '^Public-Key-Pins:' $TMPFILE)"
               out "$spaces "
          elif [[ $(grep -aci '^Public-Key-Pins:' $TMPFILE) -eq 1 ]]; then
               first_hpkp_header="$(grep -ai '^Public-Key-Pins:' $TMPFILE)"
          else
               outln "Public-Key-Pins-Only detected"
               first_hpkp_header="$(grep -ai '^Public-Key-Pins-Report-Only:' $TMPFILE)"
               out "$spaces "
               fileout "HPKP_SPKIs" "INFO" "Only Public-Key-Pins-Report-Only"
          fi

          # remove leading Public-Key-Pins* and convert it to multiline arg
          sed -e 's/Public-Key-Pins://g' -e s'/Public-Key-Pins-Report-Only://' <<< "$first_hpkp_header" | \
               tr ';' '\n' | sed -e 's/\"//g' -e 's/^ //' >$TMPFILE

          hpkp_nr_keys=$(grep -ac pin-sha $TMPFILE)
          if [[ $hpkp_nr_keys -eq 1 ]]; then
               pr_svrty_high "Only one key pinned (NOT ok), means the site may become unavailable in the future, "
               fileout "HPKP_SPKIs" "HIGH" "Only one key pinned"
               set_grade_cap "A" "Problems with HTTP Public Key Pinning (HPKP)"
          else
               pr_svrty_good "$hpkp_nr_keys"
               out " keys, "
               fileout "HPKP_SPKIs" "OK" "$hpkp_nr_keys keys pinned in header"
          fi

          # print key=value pair with awk, then strip non-numbers, to be improved with proper parsing of key-value with awk
          if "$HAS_SED_E"; then
               hpkp_age_sec=$(awk -F= '/max-age/{max_age=$2; print max_age}' $TMPFILE | sed -E 's/[^[:digit:]]//g')
          else
               hpkp_age_sec=$(awk -F= '/max-age/{max_age=$2; print max_age}' $TMPFILE | sed -r 's/[^[:digit:]]//g')
          fi
          hpkp_age_days=$((hpkp_age_sec / 86400))
          if [[ $hpkp_age_sec -ge $HPKP_MIN ]]; then
               pr_svrty_good "$hpkp_age_days days" ; out "=$hpkp_age_sec s"
               fileout "HPKP_age" "OK" "HPKP age is set to $hpkp_age_days days ($hpkp_age_sec sec)"
          else
               out "$hpkp_age_sec s = "
               pr_svrty_medium "$hpkp_age_days days (< $HPKP_MIN s = $((HPKP_MIN / 86400)) days is not good enough)"
               fileout "HPKP_age" "MEDIUM" "age is set to $hpkp_age_days days ($hpkp_age_sec sec) < $HPKP_MIN s = $((HPKP_MIN / 86400)) days is not good enough."
               set_grade_cap "A" "Problems with HTTP Public Key Pinning (HPKP)"
          fi

          if includeSubDomains "$TMPFILE"; then
               fileout "HPKP_subdomains" "INFO" "is valid for subdomains as well"
          else
               fileout "HPKP_subdomains" "INFO" "is valid for this domain only"
          fi
          if preload "$TMPFILE"; then
               fileout "HPKP_preload" "INFO" "IS marked for browser preloading"
          else
               fileout "HPKP_preload" "INFO" "NOT marked for browser preloading"
          fi

          # Get the SPKIs first
          spki=$(tr ';' '\n' < $TMPFILE | tr -d ' ' | tr -d '\"' | awk -F'=' '/pin.*=/ { print $2 }')
          debugme tmln_out "\n$spki"

          # Look at the host certificate first
          if [[ ! -s "$HOSTCERT" ]]; then
               get_host_cert || return 1
               # no host certificate
          fi

          hpkp_spki_hostcert="$($OPENSSL x509 -in $HOSTCERT -pubkey -noout 2>/dev/null | grep -v PUBLIC | \
               $OPENSSL base64 -d 2>/dev/null | $OPENSSL dgst -sha256 -binary 2>/dev/null | $OPENSSL base64 2>/dev/null)"
          hpkp_ca="$($OPENSSL x509 -in $HOSTCERT -issuer -noout 2>/dev/null |sed 's/^.*CN=//' | sed 's/\/.*$//')"

          # Get keys/hashes from intermediate certificates
          $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS $PROXY -showcerts -connect $NODEIP:$PORT $SNI")  </dev/null >$TMPFILE 2>$ERRFILE
          # Place the server's certificate in $HOSTCERT and any intermediate
          # certificates that were provided in $TEMPDIR/intermediatecerts.pem
          # https://backreference.org/2010/05/09/ocsp-verification-with-openssl/
          awk -v n=-1 "/Certificate chain/ {start=1}
                  /-----BEGIN CERTIFICATE-----/{ if (start) {inc=1; n++} }
                  inc { print > (\"$TEMPDIR/level\" n \".crt\") }
                  /---END CERTIFICATE-----/{ inc=0 }" $TMPFILE
          nrsaved=$(count_words "$(echo $TEMPDIR/level?.crt 2>/dev/null)")
          rm $TEMPDIR/level0.crt 2>/dev/null

          printf ""> "$TEMPDIR/intermediate.hashes"
          if [[ $nrsaved -ge 2 ]]; then
               for cert_fname in $TEMPDIR/level?.crt; do
                    hpkp_spki_ca="$($OPENSSL x509 -in "$cert_fname" -pubkey -noout 2>/dev/null | grep -v PUBLIC | $OPENSSL base64 -d 2>/dev/null |
                         $OPENSSL dgst -sha256 -binary 2>/dev/null | $OPENSSL enc -base64 2>/dev/null)"
                    hpkp_name="$(get_cn_from_cert $cert_fname)"
                    hpkp_ca="$($OPENSSL x509 -in $cert_fname -issuer -noout 2>/dev/null |sed 's/^.*CN=//' | sed 's/\/.*$//')"
                    [[ -n $hpkp_name ]] || hpkp_name=$($OPENSSL x509 -in "$cert_fname" -subject -noout 2>/dev/null | sed 's/^subject= //')
                    echo "$hpkp_spki_ca $hpkp_name" >> "$TEMPDIR/intermediate.hashes"
               done
          fi

          # This is where the matching magic starts. First host, intermediate, then root certificate from the supplied stores
          spki_match=false
          has_backup_spki=false
          i=0
          for hpkp_spki in $spki; do
               certificate_found=false
               # compare collected SPKIs against the host certificate
               if [[ "$hpkp_spki_hostcert" == "$hpkp_spki" ]] || [[ "$hpkp_spki_hostcert" == "$hpkp_spki=" ]]; then
                    certificate_found=true       # We have a match
                    spki_match=true
                    out "\n$spaces_indented Host cert: "
                    pr_svrty_good "$hpkp_spki"
                    fileout "HPKP_$hpkp_spki" "OK" "SPKI $hpkp_spki matches the host certificate"
               fi
               debugme tm_out "\n  $hpkp_spki | $hpkp_spki_hostcert"

               # Check for intermediate match
               if ! "$certificate_found"; then
                    hpkp_matches=$(grep "$hpkp_spki" $TEMPDIR/intermediate.hashes 2>/dev/null)
                    if [[ -n $hpkp_matches ]]; then    # hpkp_matches + hpkp_spki + '='
                         # We have a match
                         certificate_found=true
                         spki_match=true
                         out "\n$spaces_indented Sub CA:    "
                         pr_svrty_good "$hpkp_spki"
                         ca_cn="$(sed "s/^[a-zA-Z0-9\+\/]*=* *//" <<< $"$hpkp_matches" )"
                         pr_italic " $ca_cn"
                         fileout "HPKP_$hpkp_spki" "OK" "SPKI $hpkp_spki matches Intermediate CA \"$ca_cn\" pinned in the HPKP header"
                    fi
               fi

               # we compare now against a precompiled list of SPKIs against the ROOT CAs we have in $ca_hashes
               if ! "$certificate_found"; then
                    hpkp_matches=$(grep -h "$hpkp_spki" $ca_hashes 2>/dev/null | sort -u)
                    if [[ -n $hpkp_matches ]]; then
                         certificate_found=true      # root CA found
                         spki_match=true
                         if [[ $(count_lines "$hpkp_matches") -eq 1 ]]; then
                              # replace by awk
                              match_ca=$(sed "s/[a-zA-Z0-9\+\/]*=* *//" <<< "$hpkp_matches")
                         else
                              match_ca=""

                         fi
                         ca_cn="$(sed "s/^[a-zA-Z0-9\+\/]*=* *//" <<< $"$hpkp_matches" )"
                         if [[ "$match_ca" == "$hpkp_ca" ]]; then          # part of the chain
                              out "\n$spaces_indented Root CA:   "
                              pr_svrty_good "$hpkp_spki"
                              pr_italic " $ca_cn"
                              fileout "HPKP_$hpkp_spki" "INFO" "SPKI $hpkp_spki matches Root CA \"$ca_cn\" pinned. (Root CA part of the chain)"
                         else                                              # not part of chain
                              match_ca=""
                              has_backup_spki=true                         # Root CA outside the chain --> we save it for unmatched
                              fileout "HPKP_$hpkp_spki" "INFO" "SPKI $hpkp_spki matches Root CA \"$ca_cn\" pinned. (Root backup SPKI)"
                              backup_spki[i]="$(strip_lf "$hpkp_spki")"    # save it for later
                              backup_spki_str[i]="$ca_cn"                  # also the name=CN of the root CA
                              i=$((i + 1))
                         fi
                    fi
               fi

               # still no success --> it's probably a backup SPKI
               if ! "$certificate_found"; then
                    # Most likely a backup SPKI, unfortunately we can't tell for what it is: host, intermediates
                    has_backup_spki=true
                    backup_spki[i]="$(strip_lf "$hpkp_spki")"     # save it for later
                    backup_spki_str[i]=""                        # no root ca
                    i=$((i + 1))
                    fileout "HPKP_$hpkp_spki" "INFO" "SPKI $hpkp_spki doesn't match anything. This is ok for a backup for any certificate"
                    # CSV/JSON output here for the sake of simplicity, rest we do en bloc below
               fi
          done

          # now print every backup spki out we saved before
          out "\n$spaces_indented Backups:   "

          # for i=0 manually do the same as below as there's other indentation here
          if [[ -n "${backup_spki_str[0]}" ]]; then
               pr_svrty_good "${backup_spki[0]}"
               #out " Root CA: "
               prln_italic " ${backup_spki_str[0]}"
          else
               outln "${backup_spki[0]}"
          fi
          # now for i=1
          for ((i=1; i < ${#backup_spki[@]} ;i++ )); do
               if [[ -n "${backup_spki_str[i]}" ]]; then
                    # it's a Root CA outside the chain
                    pr_svrty_good "$spaces_indented            ${backup_spki[i]}"
                    #out " Root CA: "
                    prln_italic " ${backup_spki_str[i]}"
               else
                    outln "$spaces_indented            ${backup_spki[i]}"
               fi
          done
          if [[ ! -f "$ca_hashes" ]] && "$spki_match"; then
               out "$spaces "
               prln_warning "Attribution of further hashes couldn't be done as $ca_hashes could not be found"
               fileout "HPKP_SPKImatch" "WARN" "Attribution of further hashes possible as $ca_hashes could not be found"
          fi

          # If all else fails...
          if ! "$spki_match"; then
               "$has_backup_spki" && out "$spaces"       # we had a few lines with backup SPKIs already
               prln_svrty_high " No matching key for SPKI found "
               fileout "HPKP_SPKImatch" "HIGH" "None of the SPKI match your host certificate, intermediate CA or known root CAs. Bricked site?"
               set_grade_cap "A" "Problems with HTTP Public Key Pinning (HPKP)"
          fi

          if ! "$has_backup_spki"; then
               prln_svrty_high " No backup keys found. Loss/compromise of the currently pinned key(s) will lead to bricked site. "
               fileout "HPKP_backup" "HIGH" "No backup keys found. Loss/compromise of the currently pinned key(s) will lead to bricked site."
               set_grade_cap "A" "Problems with HTTP Public Key Pinning (HPKP)"
          fi
     else
          outln "--"
          fileout "HPKP" "INFO" "No support for HTTP Public Key Pinning"
     fi

     tmpfile_handle ${FUNCNAME[0]}.txt
     return 0
}

emphasize_stuff_in_headers(){
     local html_brown="<span style=\\\"color:olive;\\\">"
     local html_yellow="<span style=\\\"color:olive;font-weight:bold;\\\">"
     local html_off="<\\/span>"

# see https://www.grymoire.com/Unix/Sed.html#uh-3
#    outln "$1" | sed "s/[0-9]*/$brown&${off}/g"
     tmln_out "$1" | sed -e "s/\([0-9]\)/${brown}\1${off}/g" \
          -e "s/Unix/${yellow}Unix${off}/g" \
          -e "s/Debian/${yellow}Debian${off}/g" \
          -e "s/Win32/${yellow}Win32${off}/g" \
          -e "s/Win64/${yellow}Win64${off}/g" \
          -e "s/Ubuntu/${yellow}Ubuntu${off}/g" \
          -e "s/ubuntu/${yellow}ubuntu${off}/g" \
          -e "s/buster/${yellow}buster${off}/g" \
          -e "s/stretch/${yellow}stretch${off}/g" \
          -e "s/jessie/${yellow}jessie${off}/g" \
          -e "s/squeeze/${yellow}squeeze${off}/g" \
          -e "s/wheezy/${yellow}wheezy${off}/g" \
          -e "s/lenny/${yellow}lenny${off}/g" \
          -e "s/SUSE/${yellow}SUSE${off}/g" \
          -e "s/Red Hat Enterprise Linux/${yellow}Red Hat Enterprise Linux${off}/g" \
          -e "s/Red Hat/${yellow}Red Hat${off}/g" \
          -e "s/CentOS/${yellow}CentOS${off}/g" \
          -e "s/Via/${yellow}Via${off}/g" \
          -e "s/X-Forwarded/${yellow}X-Forwarded${off}/g" \
          -e "s/X-TYPO3-Parsetime/${yellow}X-TYPO3-Parsetime${off}/g" \
          -e "s/Liferay-Portal/${yellow}Liferay-Portal${off}/g" \
          -e "s/X-Cache-Lookup/${yellow}X-Cache-Lookup${off}/g" \
          -e "s/X-Cache/${yellow}X-Cache${off}/g" \
          -e "s/X-Squid/${yellow}X-Squid${off}/g" \
          -e "s/X-Server/${yellow}X-Server${off}/g" \
          -e "s/X-Varnish/${yellow}X-Varnish${off}/g" \
          -e "s/X-OWA-Version/${yellow}X-OWA-Version${off}/g" \
          -e "s/MicrosoftSharePointTeamServices/${yellow}MicrosoftSharePointTeamServices${off}/g" \
          -e "s/X-Application-Context/${yellow}X-Application-Context${off}/g" \
          -e "s/X-Version/${yellow}X-Version${off}/g" \
          -e "s/X-Powered-By/${yellow}X-Powered-By${off}/g" \
          -e "s/X-UA-Compatible/${yellow}X-UA-Compatible${off}/g" \
          -e "s/Link/${yellow}Link${off}/g" \
          -e "s/X-Rack-Cache/${yellow}X-Rack-Cache${off}/g" \
          -e "s/X-Runtime/${yellow}X-Runtime${off}/g" \
          -e "s/X-Pingback/${yellow}X-Pingback${off}/g" \
          -e "s/X-Permitted-Cross-Domain-Policies/${yellow}X-Permitted-Cross-Domain-Policies${off}/g" \
          -e "s/X-AspNet-Version/${yellow}X-AspNet-Version${off}/g" \
          -e "s/x-note/${yellow}x-note${off}/g" \
          -e "s/x-global-transaction-id/${yellow}x-global-transaction-id${off}/g" \
          -e "s/X-Global-Transaction-ID/${yellow}X-Global-Transaction-ID${off}/g" \
          -e "s/Alt-Svc/${yellow}Alt-Svc${off}/g" \
          -e "s/system-wsgw-management-loopback/${yellow}system-wsgw-management-loopback${off}/g"

     if "$do_html"; then
          if [[ $COLOR -ge 2 ]]; then
               html_out "$(tm_out "$1" | sed -e 's/\&/\&amp;/g' \
                    -e 's/</\&lt;/g' -e 's/>/\&gt;/g' -e 's/"/\&quot;/g' -e "s/'/\&apos;/g" \
                    -e "s/\([0-9]\)/${html_brown}\1${html_off}/g" \
                    -e "s/Unix/${html_yellow}Unix${html_off}/g" \
                    -e "s/Debian/${html_yellow}Debian${html_off}/g" \
                    -e "s/Win32/${html_yellow}Win32${html_off}/g" \
                    -e "s/Win64/${html_yellow}Win64${html_off}/g" \
                    -e "s/Ubuntu/${html_yellow}Ubuntu${html_off}/g" \
                    -e "s/ubuntu/${html_yellow}ubuntu${html_off}/g" \
                    -e "s/buster/${html_yellow}buster${html_off}/g" \
                    -e "s/stretch/${html_yellow}stretch${html_off}/g" \
                    -e "s/jessie/${html_yellow}jessie${html_off}/g" \
                    -e "s/squeeze/${html_yellow}squeeze${html_off}/g" \
                    -e "s/wheezy/${html_yellow}wheezy${html_off}/g" \
                    -e "s/lenny/${html_yellow}lenny${html_off}/g" \
                    -e "s/SUSE/${html_yellow}SUSE${html_off}/g" \
                    -e "s/Red Hat Enterprise Linux/${html_yellow}Red Hat Enterprise Linux${html_off}/g" \
                    -e "s/Red Hat/${html_yellow}Red Hat${html_off}/g" \
                    -e "s/CentOS/${html_yellow}CentOS${html_off}/g" \
                    -e "s/Via/${html_yellow}Via${html_off}/g" \
                    -e "s/X-Forwarded/${html_yellow}X-Forwarded${html_off}/g" \
                    -e "s/X-TYPO3-Parsetime/${yellow}X-TYPO3-Parsetime${html_off}/g" \
                    -e "s/Liferay-Portal/${html_yellow}Liferay-Portal${html_off}/g" \
                    -e "s/X-Cache-Lookup/${html_yellow}X-Cache-Lookup${html_off}/g" \
                    -e "s/X-Cache/${html_yellow}X-Cache${html_off}/g" \
                    -e "s/X-Squid/${html_yellow}X-Squid${html_off}/g" \
                    -e "s/X-Server/${html_yellow}X-Server${html_off}/g" \
                    -e "s/X-Varnish/${html_yellow}X-Varnish${html_off}/g" \
                    -e "s/X-OWA-Version/${html_yellow}X-OWA-Version${html_off}/g" \
                    -e "s/MicrosoftSharePointTeamServices/${html_yellow}MicrosoftSharePointTeamServices${html_off}/g" \
                    -e "s/X-Application-Context/${html_yellow}X-Application-Context${html_off}/g" \
                    -e "s/X-Version/${html_yellow}X-Version${html_off}/g" \
                    -e "s/X-Powered-By/${html_yellow}X-Powered-By${html_off}/g" \
                    -e "s/X-UA-Compatible/${html_yellow}X-UA-Compatible${html_off}/g" \
                    -e "s/Link/${html_yellow}Link${html_off}/g" \
                    -e "s/X-Runtime/${html_yellow}X-Runtime${html_off}/g" \
                    -e "s/X-Rack-Cache/${html_yellow}X-Rack-Cache${html_off}/g" \
                    -e "s/X-Pingback/${html_yellow}X-Pingback${html_off}/g" \
                    -e "s/X-Permitted-Cross-Domain-Policies/${html_yellow}X-Permitted-Cross-Domain-Policies${html_off}/g" \
                    -e "s/X-AspNet-Version/${html_yellow}X-AspNet-Version${html_off}/g")" \
                    -e "s/x-note/${html_yellow}x-note${html_off}/g" \
                    -e "s/X-Global-Transaction-ID/${html_yellow}X-Global-Transaction-ID${html_off}/g" \
                    -e "s/x-global-transaction-id/${html_yellow}x-global-transaction-id${html_off}/g" \
                    -e "s/Alt-Svc/${html_yellow}Alt-Svc${html_off}/g" \
                    -e "s/system-wsgw-management-loopback/${html_yellow}system-wsgw-management-loopback${html_off}/g"
#FIXME: this is double code. The pattern to emphasize would fit better into
# one function.
# Also we need another function like run_other_header as otherwise "Link" "Alt-Svc" will never be found.
# And: I matches case sensitive only which might not detect all banners. (sed ignorecase is not possible w/ BSD sed)
          else
               html_out "$(html_reserved "$1")"
          fi
          html_out "\n"
     fi
}

run_server_banner() {
     local serverbanner
     local jsonID="banner_server"

     if [[ ! -s $HEADERFILE ]]; then
          run_http_header "$1" || return 1
     fi
     pr_bold " Server banner                "
     grep -ai '^Server' $HEADERFILE >$TMPFILE
     if [[ $? -eq 0 ]]; then
          serverbanner=$(sed -e 's/^Server: //' -e 's/^server: //' $TMPFILE)
          if [[ "$serverbanner" == $'\n' ]] || [[ "$serverbanner" == $'\r' ]] || [[ "$serverbanner" == $'\n\r' ]] || [[ -z "$serverbanner" ]]; then
               outln "exists but empty string"
               fileout "$jsonID" "INFO" "Server banner is empty"
          else
               emphasize_stuff_in_headers "$serverbanner"
               fileout "$jsonID" "INFO" "$serverbanner"
               if [[ "$serverbanner" == *Microsoft-IIS/6.* ]] && [[ $OSSL_VER == 1.0.2* ]]; then
                    prln_warning "                              It's recommended to run another test w/ OpenSSL 1.0.1 !"
                    # see https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892
                    fileout "${jsonID}" "WARN" "IIS6_openssl_mismatch: Recommended to rerun this test w/ OpenSSL 1.0.1. See https://github.com/PeterMosmans/openssl/issues/19#issuecomment-100897892"
               fi
          fi
          # mozilla.github.io/server-side-tls/ssl-config-generator/
          # https://support.microsoft.com/en-us/kb/245030
     else
          outln "(no \"Server\" line in header, interesting!)"
          fileout "$jsonID" "INFO" "No Server banner line in header, interesting!"
     fi

     tmpfile_handle ${FUNCNAME[0]}.txt
     return 0
}

run_appl_banner() {
     local line
     local first=true
     local spaces="                              "
     local appl_banners=""
     local jsonID="banner_application"

     if [[ ! -s $HEADERFILE ]]; then
          run_http_header "$1" || return 1
     fi
     pr_bold " Application banner           "
     grep -Eai '^X-Powered-By|^X-AspNet-Version|^X-Version|^Liferay-Portal|^X-TYPO3-Parsetime|^X-OWA-Version^|^MicrosoftSharePointTeamServices' $HEADERFILE >$TMPFILE
     if [[ $? -ne 0 ]]; then
          outln "--"
          fileout "$jsonID" "INFO" "No application banner found"
     else
          while IFS='' read -r line; do
               line=$(strip_lf "$line")
               if ! $first; then
                    out "$spaces"
                    appl_banners="${appl_banners}, ${line}"
               else
                    appl_banners="${line}"
                    first=false
               fi
               emphasize_stuff_in_headers "$line"
          done < "$TMPFILE"
          fileout "$jsonID" "INFO" "$appl_banners"
     fi
     tmpfile_handle ${FUNCNAME[0]}.txt
     return 0
}

run_rp_banner() {
     local line
     local first=true
     local spaces="                              "
     local rp_banners=""
     local jsonID="banner_reverseproxy"
     local cwe="CWE-200"
     local cve=""

     if [[ ! -s $HEADERFILE ]]; then
          run_http_header "$1" || return 1
     fi
     pr_bold " Reverse Proxy banner         "
     grep -Eai '^Via:|^X-Cache|^X-Squid|^X-Varnish:|^X-Server-Name:|^X-Server-Port:|^x-forwarded|^Forwarded' $HEADERFILE >$TMPFILE
     if [[ $? -ne 0 ]]; then
          outln "--"
          fileout "$jsonID" "INFO" "--" "$cve" "$cwe"
     else
          while read line; do
               line=$(strip_lf "$line")
               if $first; then
                    first=false
               else
                    out "$spaces"
               fi
               emphasize_stuff_in_headers "$line"
               rp_banners="${rp_banners}${line}"
          done < $TMPFILE
          fileout "$jsonID" "INFO" "$rp_banners" "$cve" "$cwe"
     fi
     outln

     tmpfile_handle ${FUNCNAME[0]}.txt
     return 0
}


# arg1: multiline string w cookies
#
sub_f5_bigip_check() {
     local allcookies="$1"
     local ip port cookievalue cookiename
     local routed_domain offset
     local savedcookies=""
     local spaces="$2"
     local cwe="CWE-212"
     local cve=""

     # taken from https://github.com/drwetter/F5-BIGIP-Decoder, more details see there

     debugme echo -e "all cookies: >> $allcookies <<\n"
     while true; do IFS='=' read cookiename cookievalue
          [[ -z "$cookievalue" ]] && break
          cookievalue=${cookievalue/;/}
          debugme echo $cookiename : $cookievalue
          if grep -Eq '[0-9]{9,10}\.[0-9]{3,5}\.0000' <<< "$cookievalue"; then
               ip="$(f5_ip_oldstyle "$cookievalue")"
               port="$(f5_port_decode $cookievalue)"
               out "${spaces}F5 cookie (default IPv4 pool member): "; pr_italic "$cookiename "; prln_svrty_medium "${ip}:${port}"
               fileout "cookie_bigip_f5" "MEDIUM" "Information leakage: F5 cookie $cookiename $cookievalue is default IPv4 pool member ${ip}:${port}" "$cve" "$cwe"
          elif grep -Eq '^rd[0-9]{1,3}o0{20}f{4}[a-f0-9]{8}o[0-9]{1,5}' <<< "$cookievalue"; then
               routed_domain="$(f5_determine_routeddomain "$cookievalue")"
               offset=$(( 2 + ${#routed_domain} + 1 + 24))
               port="${cookievalue##*o}"
               ip="$(f5_hex2ip "${cookievalue:$offset:8}")"
               out "${spaces}F5 cookie (IPv4 pool in routed domain "; pr_svrty_medium "$routed_domain"; out "): "; pr_italic "$cookiename "; prln_svrty_medium "${ip}:${port}"
               fileout "cookie_bigip_f5" "MEDIUM" "Information leakage: F5 cookie $cookiename $cookievalue is IPv4 pool member in routed domain $routed_domain ${ip}:${port}" "$cve" "$cwe"
          elif grep -Eq '^vi[a-f0-9]{32}\.[0-9]{1,5}' <<< "$cookievalue"; then
               ip="$(f5_hex2ip6 ${cookievalue:2:32})"
               port="${cookievalue##*.}"
               port=$(f5_port_decode "$port")
               out "${spaces}F5 cookie (default IPv6 pool member): "; pr_italic "$cookiename "; prln_svrty_medium "${ip}:${port}"
               fileout "cookie_bigip_f5" "MEDIUM" "Information leakage: F5 cookie $cookiename $cookievalue is default IPv6 pool member ${ip}:${port}" "$cve" "$cwe"
          elif grep -Eq '^rd[0-9]{1,3}o[a-f0-9]{32}o[0-9]{1,5}' <<< "$cookievalue"; then
               routed_domain="$(f5_determine_routeddomain "$cookievalue")"
               offset=$(( 2 + ${#routed_domain} + 1 ))
               port="${cookievalue##*o}"
               ip="$(f5_hex2ip6 ${cookievalue:$offset:32})"
               out "${spaces}F5 cookie (IPv6 pool in routed domain "; pr_svrty_medium "$routed_domain"; out "): "; pr_italic "$cookiename "; prln_svrty_medium "${ip}:${port}"
               fileout "cookie_bigip_f5" "MEDIUM" "Information leakage: F5 cookie $cookiename $cookievalue is IPv6 pool member in routed domain $routed_domain ${ip}:${port}" "$cve" "$cwe"
          elif grep -Eq '^\!.*=$' <<< "$cookievalue"; then
               if [[ "${#cookievalue}" -eq 81 ]] ; then
                    savedcookies="${savedcookies}     ${cookiename}=${cookievalue:1:79}"
                    out "${spaces}Encrypted F5 cookie named "; pr_italic "${cookiename}"; outln " detected"
                    fileout "cookie_bigip_f5" "INFO" "encrypted F5 cookie named ${cookiename}"
               fi
          fi
     done <<< "$allcookies"
}


run_cookie_flags() {     # ARG1: Path
     local -i nr_cookies
     local -i nr_httponly nr_secure
     local negative_word
     local msg302="" msg302_=""
     local spaces="                              "

     if [[ ! -s $HEADERFILE ]]; then
          run_http_header "$1" || return 1
     fi

     if [[ ! "$HTTP_STATUS_CODE" =~ 20 ]]; then
          if [[ "$HTTP_STATUS_CODE" =~ [301|302] ]]; then
               msg302=" -- maybe better try target URL of 30x"
               msg302_=" (30x detected, better try target URL of 30x)"
          else
               msg302=" -- HTTP status $HTTP_STATUS_CODE signals you maybe missed the web application"
               msg302_=" (maybe missed the application)"
          fi
     fi

     pr_bold " Cookie(s)                    "
     grep -ai '^Set-Cookie' $HEADERFILE >$TMPFILE
     if [[ $? -ne 0 ]]; then
          outln "(none issued at \"$1\")$msg302"
          fileout "cookie_count" "INFO" "0 at \"$1\"$msg302_"
     else
          nr_cookies=$(count_lines "$(cat $TMPFILE)")
          out "$nr_cookies issued: "
          fileout "cookie_count" "INFO" "$nr_cookies at \"$1\"$msg302_"
          if [[ $nr_cookies -gt 1 ]]; then
               negative_word="NONE"
          else
               negative_word="NOT"
          fi
          nr_secure=$(grep -iac secure $TMPFILE)
          case $nr_secure in
               0) pr_svrty_medium "$negative_word" ;;
               [123456789]) pr_svrty_good "$nr_secure/$nr_cookies";;
          esac
          out " secure, "
          if [[ $nr_cookies -eq $nr_secure ]]; then
               fileout "cookie_secure" "OK" "All ($nr_cookies) at \"$1\" marked as secure"
          else
               fileout "cookie_secure" "INFO" "$nr_secure/$nr_cookies at \"$1\" marked as secure"
          fi
          nr_httponly=$(grep -cai httponly $TMPFILE)
          case $nr_httponly in
               0) pr_svrty_medium "$negative_word" ;;
               [123456789]) pr_svrty_good "$nr_httponly/$nr_cookies";;
          esac
          out " HttpOnly"
          if [[ $nr_cookies -eq $nr_httponly ]]; then
               fileout "cookie_httponly" "OK" "All ($nr_cookies) at \"$1\" marked as HttpOnly$msg302_"
          else
               fileout "cookie_httponly" "INFO" "$nr_secure/$nr_cookies at \"$1\" marked as HttpOnly$msg302_"
          fi
          outln "$msg302"
          allcookies="$(awk '/[Ss][Ee][Tt]-[Cc][Oo][Oo][Kk][Ii][Ee]:/ { print $2 }' "$TMPFILE")"
          sub_f5_bigip_check "$allcookies" "$spaces"
     fi

     tmpfile_handle ${FUNCNAME[0]}.txt
     return 0
}


run_security_headers() {
     local good_header="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 Expect-CT"
     local other_header="Access-Control-Allow-Origin Upgrade X-Served-By Referrer-Policy X-UA-Compatible Cache-Control Pragma"
     local header header_output
     local first=true
     local spaces="                              "
     local have_header=false

     if [[ ! -s $HEADERFILE ]]; then
          run_http_header "$1" || return 1
     fi

     pr_bold " Security headers             "
     for header in $good_header; do
          [[ "$DEBUG" -ge 5 ]] &&  echo "testing \"$header\""
          match_httpheader_key "$header" "$header" "$spaces" "$first"
          if [[ $? -ge 1 ]]; then
               have_header=true
               if "$first"; then
                    first=false
               fi
               # Include $header when determining where to insert line breaks, but print $header
               # separately.
               pr_svrty_good "$header"
               header_output="$(out_row_aligned_max_width "${header:2} $HEADERVALUE" "$spaces  " $TERM_WIDTH)"
               outln "${header_output#${header:2}}"
               fileout "$header" "OK" "$HEADERVALUE"
          fi
     done

     for header in $other_header; do
          [[ "$DEBUG" -ge 5 ]] &&  echo "testing \"$header\""
          match_httpheader_key "$header" "$header" "$spaces" "$first"
          if [[ $? -ge 1 ]]; then
               have_header=true
               if "$first"; then
                    first=false
               fi
               pr_litecyan "$header"
               outln " $HEADERVALUE"     # shouldn't be that long
               fileout "$header" "INFO" "$HEADERVALUE"
          fi
     done
     #TODO: I am not testing for the correctness or anything stupid yet, e.g. "X-Frame-Options: allowall" or Access-Control-Allow-Origin: *

     if ! "$have_header"; then
          prln_svrty_medium "--"
          fileout "Security headers" "MEDIUM" "Review HTTP response headers for security posture of the application. Proper HTTP response headers can restrict modern browsers from running into easily preventable vulnerabilities.\nRef: https://owasp.org/www-project-secure-headers/"
     fi

     tmpfile_handle ${FUNCNAME[0]}.txt
     return 0
}


# #1: string with 2 opensssl codes, output is same in NSS/ssllabs terminology
normalize_ciphercode() {
     if [[ "${1:2:2}" == "00" ]]; then
          tm_out "$(tolower "x${1:7:2}")"
     else
          tm_out "$(tolower "x${1:2:2}${1:7:2}${1:12:2}")"
     fi
     return 0
}

prettyprint_local() {
     local arg line
     local hexc hexcode dash ciph sslvers kx auth enc mac export
     local re='^[0-9A-Fa-f]+$'

     if [[ "$1" == 0x* ]] || [[ "$1" == 0X* ]]; then
          fatal "pls supply x<number> instead" $ERR_CMDLINE
     fi

     if [[ -z "$1" ]]; then
          pr_headline " Displaying all $OPENSSL_NR_CIPHERS local ciphers ";
     else
          pr_headline " Displaying all local ciphers ";
          # pattern provided; which one?
          [[ $1 =~ $re ]] && \
               pr_headline "matching number pattern \"$1\" " || \
               pr_headline "matching word pattern "\"$1\"" (ignore case) "
     fi
     outln "\n"
     neat_header

     if [[ -z "$1" ]]; then
          while read -r hexcode dash ciph sslvers kx auth enc mac export ; do
               hexc="$(normalize_ciphercode $hexcode)"
               outln "$(neat_list "$hexc" "$ciph" "$kx" "$enc" "$export")"
          done < <(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 'ALL' "-V")  # -V doesn't work with openssl < 1.0
     else
          #for arg in $(echo $@ | sed 's/,/ /g'); do
          for arg in ${*//,/ /}; do
               while read -r hexcode dash ciph sslvers kx auth enc mac export ; do
                    hexc="$(normalize_ciphercode $hexcode)"
                    # for numbers we don't do word matching:
                    [[ $arg =~ $re ]] && \
                         line="$(neat_list "$hexc" "$ciph" "$kx" "$enc" "$export" | grep -ai "$arg")" || \
                         line="$(neat_list "$hexc" "$ciph" "$kx" "$enc" "$export" | grep -wai "$arg")"
                    [[ -n "$line" ]] && outln "$line"
               done < <(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 'ALL' "-V") # -V doesn't work with openssl < 1.0
          done
     fi
     outln
     return 0
}


# Generic function for a rated output, no used yet.
# arg1: rating from 2 to -4 if available or not
# arg2: no/yes: decides whether positive or negative logic will be applied and "not" will be printed
# arg3: jsonID
#
rated_output() {
     local jsonID=$3
     local logic=""

     if [[ $2 == no ]] || [[ $2 == negative ]]; then
          logic="not "
     fi
     case $1 in
          2)   pr_svrty_best "${logic}offered (OK)"
               fileout "${jsonID}" "OK" "${logic}offered"
               ;;
          1)   pr_svrty_good "${logic}offered (OK)"
               fileout "${jsonID}" "OK" "${logic}offered"
               ;;
          0)   out "${logic}offered"
               fileout "${jsonID}" "INFO" "${logic}offered"
               ;;
          -1)  pr_svrty_low "${logic}offered"
               fileout "${jsonID}" "LOW" "${logic}offered"
               ;;
          -2)  pr_svrty_medium "${logic}offered"
               fileout "${jsonID}" "MEDIUM" "${logic}offered"
               ;;
          -3)  pr_svrty_high "${logic}offered (NOT ok)"
               fileout "${jsonID}" "HIGH" "${logic}offered"
               ;;
          -4)  pr_svrty_critical "${logic}offered (NOT ok)"
               fileout "${jsonID}" "CRITICAL" "${logic}offered"
               ;;
          *)   pr_warning "FIXME: error around $LINENO, (please report this)"
               fileout "${jsonID}" "WARN" "return condition $2 when $1 unclear"
               return 1
               ;;
     esac
     return 0
}


openssl2rfc() {
     local rfcname=""
     local -i i

     for (( i=0; i < TLS_NR_CIPHERS; i++ )); do
          [[ "$1" == ${TLS_CIPHER_OSSL_NAME[i]} ]] && rfcname="${TLS_CIPHER_RFC_NAME[i]}" && break
     done
     [[ "$rfcname" == "-" ]] && rfcname=""
     [[ -n "$rfcname" ]] && tm_out "$rfcname"
     return 0
}

rfc2openssl() {
     local ossl_name
     local -i i

     for (( i=0; i < TLS_NR_CIPHERS; i++ )); do
          [[ "$1" == ${TLS_CIPHER_RFC_NAME[i]} ]] && ossl_name="${TLS_CIPHER_OSSL_NAME[i]}" && break
     done
     [[ "$ossl_name" == "-" ]] && ossl_name=""
     [[ -n "$ossl_name" ]] && tm_out "$ossl_name"
     return 0
}

openssl2hexcode() {
     local hexc=""
     local -i i

     if [[ $TLS_NR_CIPHERS -eq 0 ]]; then
          if "$HAS_CIPHERSUITES"; then
               hexc="$($OPENSSL ciphers -V -ciphersuites "$TLS13_OSSL_CIPHERS" 'ALL:COMPLEMENTOFALL:@STRENGTH' | awk '/ '"$1"' / { print $1 }')"
          else
               hexc="$($OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL:@STRENGTH' | awk '/ '"$1"' / { print $1 }')"
          fi
     else
          for (( i=0; i < TLS_NR_CIPHERS; i++ )); do
               [[ "$1" == ${TLS_CIPHER_OSSL_NAME[i]} ]] && hexc="${TLS_CIPHER_HEXCODE[i]}" && break
          done
     fi
     [[ -z "$hexc" ]] && return 1
     tm_out "$hexc"
     return 0
}

rfc2hexcode() {
     local hexc=""
     local -i i

     for (( i=0; i < TLS_NR_CIPHERS; i++ )); do
          [[ "$1" == "${TLS_CIPHER_RFC_NAME[i]}" ]] && hexc="${TLS_CIPHER_HEXCODE[i]}" && break
     done
     [[ -z "$hexc" ]] && return 1
     tm_out "$hexc"
     return 0
}

show_rfc_style(){
     local rfcname="" hexcode
     local -i i

     hexcode="$(toupper "$1")"
     case ${#hexcode} in
          3) hexcode="0x00,0x${hexcode:1:2}" ;;
          5) hexcode="0x${hexcode:1:2},0x${hexcode:3:2}" ;;
          7) hexcode="0x${hexcode:1:2},0x${hexcode:3:2},0x${hexcode:5:2}" ;;
          *) return 1 ;;
     esac
     for (( i=0; i < TLS_NR_CIPHERS; i++ )); do
          [[ "$hexcode" == ${TLS_CIPHER_HEXCODE[i]} ]] && rfcname="${TLS_CIPHER_RFC_NAME[i]}" && break
     done
     [[ "$rfcname" == "-" ]] && rfcname=""
     [[ -n "$rfcname" ]] && tm_out "$rfcname"
     return 0
}

neat_header(){
     if [[ "$DISPLAY_CIPHERNAMES" =~ rfc ]]; then
          out "$(printf -- "Hexcode  Cipher Suite Name (IANA/RFC)                      KeyExch.   Encryption  Bits")"
          [[ "$DISPLAY_CIPHERNAMES" != rfc-only ]] && out "$(printf -- "     Cipher Suite Name (OpenSSL)")"
          outln
          out "$(printf -- "%s------------------------------------------------------------------------------------------")"
          [[ "$DISPLAY_CIPHERNAMES" != rfc-only ]] && out "$(printf -- "---------------------------------------")"
          outln
     else
          out "$(printf -- "Hexcode  Cipher Suite Name (OpenSSL)       KeyExch.   Encryption  Bits")"
          [[ "$DISPLAY_CIPHERNAMES" != openssl-only ]] && out "$(printf -- "     Cipher Suite Name (IANA/RFC)")"
          outln
          out "$(printf -- "%s--------------------------------------------------------------------------")"
          [[ "$DISPLAY_CIPHERNAMES" != openssl-only ]] && out "$(printf -- "---------------------------------------------------")"
          outln
     fi
}


# arg1: hexcode
# arg2: cipher in openssl notation
# arg3: keyexchange
# arg4: encryption (maybe included "export")
# arg5: "export" if the cipher is an export-quality cipher, empty otherwise.
# arg6: "true" if the cipher's "quality" should be highlighted
#       "false" if the line should be printed in light grey
#       empty if line should be returned as a string
neat_list(){
     local hexcode="$1"
     local ossl_cipher="$2" export="$5" tls_cipher=""
     local kx enc strength line what_dh bits
     local -i i len

     kx="${3//Kx=/}"
     enc="${4//Enc=/}"
     # In two cases LibreSSL uses very long names for encryption algorithms
     # and doesn't include the number of bits.
     [[ "$enc" == ChaCha20-Poly1305 ]] && enc="CHACHA20(256)"
     [[ "$enc" == GOST-28178-89-CNT ]] && enc="GOST(256)"

     strength="${enc//\)/}"             # retrieve (). first remove traling ")"
     strength="${strength#*\(}"         # exfiltrate (VAL
     enc="${enc%%\(*}"

     enc="${enc//POLY1305/}"            # remove POLY1305
     enc="${enc//\//}"                  # remove "/"

     # For rating set bit size
     set_ciph_str_score $strength

     [[ "$export" =~ export ]] && strength="$strength,exp"

     [[ "$DISPLAY_CIPHERNAMES" != openssl-only ]] && tls_cipher="$(show_rfc_style "$hexcode")"

     if [[ "$6" != true ]]; then
          if [[ "$DISPLAY_CIPHERNAMES" =~ rfc ]]; then
               line="$(printf -- " %-7s %-49s %-10s %-12s%-8s" "$hexcode" "$tls_cipher" "$kx" "$enc" "$strength")"
               [[ "$DISPLAY_CIPHERNAMES" != rfc-only ]] && line+="$(printf -- " %-33s${SHOW_EACH_C:+  %-0s}" "$ossl_cipher")"
          else
               line="$(printf -- " %-7s %-33s %-10s %-12s%-8s" "$hexcode" "$ossl_cipher" "$kx" "$enc" "$strength")"
               [[ "$DISPLAY_CIPHERNAMES" != openssl-only ]] && line+="$(printf -- " %-49s${SHOW_EACH_C:+  %-0s}" "$tls_cipher")"
          fi
          if [[ -z "$6" ]]; then
               tm_out "$line"
          else
               pr_deemphasize "$line"
          fi
          return 0
     fi
     if [[ "$kx" =~ " " ]]; then
          what_dh="${kx%% *}"
          bits="${kx##* }"
     else
          what_dh="$kx"
          bits=""
     fi
     if [[ "$COLOR" -le 2 ]]; then
          if [[ "$DISPLAY_CIPHERNAMES" =~ rfc ]]; then
               out "$(printf -- " %-7s %-49s " "$hexcode" "$tls_cipher")"
          else
               out "$(printf -- " %-7s %-33s " "$hexcode" "$ossl_cipher")"
          fi
     else
          out "$(printf -- " %-7s " "$hexcode")"
          if [[ "$DISPLAY_CIPHERNAMES" =~ rfc ]]; then
               print_fixed_width "$tls_cipher" 49 pr_cipher_quality
          else
               print_fixed_width "$ossl_cipher" 33 pr_cipher_quality
          fi
     fi
     out "$what_dh"
     if [[ -n "$bits" ]]; then
          if [[ $what_dh == DH ]] || [[ $what_dh == EDH ]]; then
               pr_dh_quality "$bits" " $bits"
          elif [[ $what_dh == ECDH ]]; then
               pr_ecdh_quality "$bits" " $bits"
          fi
     fi
     len=${#kx}
     for (( i=len; i<10; i++ )); do
          out " "
     done
     out "$(printf -- " %-12s%-8s " "$enc" "$strength")"
     if [[ "$COLOR" -le 2 ]]; then
          if [[ "$DISPLAY_CIPHERNAMES" == rfc ]]; then
               out "$(printf -- "%-33s${SHOW_EACH_C:+  %-0s}" "$ossl_cipher")"
          elif [[ "$DISPLAY_CIPHERNAMES" == openssl ]]; then
               out "$(printf -- "%-49s${SHOW_EACH_C:+  %-0s}" "$tls_cipher")"
          fi
     else
          if [[ "$DISPLAY_CIPHERNAMES" == rfc ]]; then
               print_fixed_width "$ossl_cipher" 32 pr_cipher_quality
          elif [[ "$DISPLAY_CIPHERNAMES" == openssl ]]; then
               print_fixed_width "$tls_cipher" 48 pr_cipher_quality
          fi
          out "$(printf -- "${SHOW_EACH_C:+  %-0s}")"
     fi
}

run_cipher_match(){
     local hexc n auth ciphers_to_test tls13_ciphers_to_test supported_sslv2_ciphers s
     local -a hexcode normalized_hexcode ciph sslvers kx enc export2 sigalg
     local -a ciphers_found ciphers_found2 ciph2 rfc_ciph rfc_ciph2 ossl_supported
     local -a -i index
     local -i nr_ciphers=0 nr_ossl_ciphers=0 nr_nonossl_ciphers=0
     local -i num_bundles bundle_size bundle end_of_bundle
     local dhlen has_dh_bits="$HAS_DH_BITS"
     local cipher proto protos_to_try
     local available
     local -i sclient_success
     local re='^[0-9A-Fa-f]+$'
     local using_sockets=true

     "$SSL_NATIVE" && using_sockets=false
     "$FAST" && using_sockets=false
     [[ $TLS_NR_CIPHERS == 0 ]] && using_sockets=false

     pr_headline " Testing ciphers with "
     if [[ $1 =~ $re ]]; then
          pr_headline "matching number pattern \"$1\" "
          tjolines="$tjolines matching number pattern \"$1\"\n\n"
     else
          pr_headline "word pattern "\"$1\"" (ignore case) "
          tjolines="$tjolines word pattern \"$1\" (ignore case)\n\n"
     fi
     outln
     if ! "$using_sockets"; then
          [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && pr_warning " Cipher mapping not available, doing a fallback to openssl"
          if ! "$HAS_DH_BITS"; then
               [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && out "."
               prln_warning "    (Your $OPENSSL cannot show DH/ECDH bits)"
          fi
     fi
     outln
     neat_header
     #for arg in $(echo $@ | sed 's/,/ /g'); do
     for arg in ${*//, /}; do
          if "$using_sockets" || [[ $OSSL_VER_MAJOR -lt 1 ]]; then
               for (( i=0; i < TLS_NR_CIPHERS; i++ )); do
                    hexc="${TLS_CIPHER_HEXCODE[i]}"
                    if [[ ${#hexc} -eq 9 ]]; then
                         hexcode[nr_ciphers]="${hexc:2:2},${hexc:7:2}"
                         if [[ "${hexc:2:2}" == "00" ]]; then
                              normalized_hexcode[nr_ciphers]="x${hexc:7:2}"
                         else
                              normalized_hexcode[nr_ciphers]="x${hexc:2:2}${hexc:7:2}"
                         fi
                    else
                         hexc="$(tolower "$hexc")"
                         hexcode[nr_ciphers]="${hexc:2:2},${hexc:7:2},${hexc:12:2}"
                         normalized_hexcode[nr_ciphers]="x${hexc:2:2}${hexc:7:2}${hexc:12:2}"
                    fi
                    if [[ $arg =~ $re ]]; then
                         neat_list "${normalized_hexcode[nr_ciphers]}" "${TLS_CIPHER_OSSL_NAME[i]}" "${TLS_CIPHER_KX[i]}" "${TLS_CIPHER_ENC[i]}" "${TLS_CIPHER_EXPORT[i]}" | grep -qai "$arg"
                    else
                         neat_list "${normalized_hexcode[nr_ciphers]}" "${TLS_CIPHER_OSSL_NAME[i]}" "${TLS_CIPHER_KX[i]}" "${TLS_CIPHER_ENC[i]}" "${TLS_CIPHER_EXPORT[i]}" | grep -qwai "$arg"
                    fi
                    if [[ $? -eq 0 ]] && ( "$using_sockets" || "${TLS_CIPHER_OSSL_SUPPORTED[i]}" ); then    # string matches, so we can ssl to it:
                         normalized_hexcode[nr_ciphers]="$(tolower "${normalized_hexcode[nr_ciphers]}")"
                         ciph[nr_ciphers]="${TLS_CIPHER_OSSL_NAME[i]}"
                         rfc_ciph[nr_ciphers]="${TLS_CIPHER_RFC_NAME[i]}"
                         kx[nr_ciphers]="${TLS_CIPHER_KX[i]}"
                         enc[nr_ciphers]="${TLS_CIPHER_ENC[i]}"
                         sslvers[nr_ciphers]="${TLS_CIPHER_SSLVERS[i]}"
                         export2[nr_ciphers]="${TLS_CIPHER_EXPORT[i]}"
                         ciphers_found[nr_ciphers]=false
                         sigalg[nr_ciphers]=""
                         ossl_supported[nr_ciphers]="${TLS_CIPHER_OSSL_SUPPORTED[i]}"
                         if "$using_sockets" && ! "$has_dh_bits" && \
                            ( [[ ${kx[nr_ciphers]} == "Kx=ECDH" ]] || [[ ${kx[nr_ciphers]} == "Kx=DH" ]] || [[ ${kx[nr_ciphers]} == "Kx=EDH" ]] ); then
                              ossl_supported[nr_ciphers]=false
                         fi
                         nr_ciphers+=1
                    fi
               done
          else
               while read hexc n ciph[nr_ciphers] sslvers[nr_ciphers] kx[nr_ciphers] auth enc[nr_ciphers] mac export2[nr_ciphers]; do
                    hexc="$(normalize_ciphercode $hexc)"
                    # is argument a number?
                    if [[ $arg =~ $re ]]; then
                         neat_list "$hexc" "${ciph[nr_ciphers]}" "${kx[nr_ciphers]}" "${enc[nr_ciphers]}" "${export2[nr_ciphers]}" | grep -qai "$arg"
                    else
                         neat_list "$hexc" "${ciph[nr_ciphers]}" "${kx[nr_ciphers]}" "${enc[nr_ciphers]}" "${export2[nr_ciphers]}" | grep -qwai "$arg"
                    fi
                    if [[ $? -eq 0 ]]; then    # string matches, so we can ssl to it:
                         ciphers_found[nr_ciphers]=false
                         normalized_hexcode[nr_ciphers]="$hexc"
                         sigalg[nr_ciphers]=""
                         ossl_supported[nr_ciphers]=true
                         nr_ciphers+=1
                    fi
                    done < <(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 'ALL' "-V")
          fi

          # Test the SSLv2 ciphers, if any.
          if "$using_sockets"; then
               ciphers_to_test=""
               for (( i=0; i < nr_ciphers; i++ )); do
                    if [[ "${sslvers[i]}" == SSLv2 ]]; then
                         ciphers_to_test+=", ${hexcode[i]}"
                    fi
               done
               if [[ -n "$ciphers_to_test" ]]; then
                    sslv2_sockets "${ciphers_to_test:2}" "true"
                    if [[ $? -eq 3 ]] && [[ "$V2_HELLO_CIPHERSPEC_LENGTH" -ne 0 ]]; then
                         supported_sslv2_ciphers="$(grep "Supported cipher: " "$TEMPDIR/$NODEIP.parse_sslv2_serverhello.txt")"
                         "$SHOW_SIGALGO" && s="$(read_sigalg_from_file "$HOSTCERT")"
                         for (( i=0 ; i<nr_ciphers; i++ )); do
                              if [[ "${sslvers[i]}" == SSLv2 ]] && [[ "$supported_sslv2_ciphers" =~ ${normalized_hexcode[i]} ]]; then
                                   ciphers_found[i]=true
                                   "$SHOW_SIGALGO" && sigalg[i]="$s"
                              fi
                         done
                    fi
               fi
          else
               ciphers_to_test=""
               for (( i=0; i < nr_ciphers; i++ )); do
                    if [[ "${sslvers[i]}" == SSLv2 ]]; then
                         ciphers_to_test+=":${ciph[i]}"
                    fi
               done
               if [[ -n "$ciphers_to_test" ]]; then
                    $OPENSSL s_client -cipher "${ciphers_to_test:1}" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY -ssl2 >$TMPFILE 2>$ERRFILE </dev/null
                    sclient_connect_successful $? "$TMPFILE"
                    if [[ $? -eq 0 ]]; then
                         supported_sslv2_ciphers="$(grep -A 4 "Ciphers common between both SSL endpoints:" $TMPFILE)"
                         "$SHOW_SIGALGO" && s="$(read_sigalg_from_file "$TMPFILE")"
                         for (( i=0 ; i<nr_ciphers; i++ )); do
                              if [[ "${sslvers[i]}" == SSLv2 ]] && [[ "$supported_sslv2_ciphers" =~ ${ciph[i]} ]]; then
                                   ciphers_found[i]=true
                                   "$SHOW_SIGALGO" && sigalg[i]="$s"
                              fi
                         done
                    fi
               fi
          fi

          for (( i=0; i < nr_ciphers; i++ )); do
               if "${ossl_supported[i]}" && [[ "${sslvers[i]}" != "SSLv2" ]]; then
                    ciphers_found2[nr_ossl_ciphers]=false
                    ciph2[nr_ossl_ciphers]="${ciph[i]}"
                    index[nr_ossl_ciphers]=$i
                    nr_ossl_ciphers+=1
               fi
          done
          if [[ $nr_ossl_ciphers -eq 0 ]]; then
               num_bundles=0
          else
               # Some servers can't handle a handshake with >= 128 ciphers. So,
               # test cipher suites in bundles of 128 or less.
               num_bundles=$nr_ossl_ciphers/128
               [[ $((nr_ossl_ciphers%128)) -ne 0 ]] && num_bundles+=1

               bundle_size=$nr_ossl_ciphers/$num_bundles
               [[ $((nr_ossl_ciphers%num_bundles)) -ne 0 ]] && bundle_size+=1
          fi

          if "$HAS_TLS13"; then
               protos_to_try="-no_ssl2 -tls1_2 -tls1_1 -tls1"
          else
               protos_to_try="-no_ssl2 -tls1_1 -tls1"
          fi
          "$HAS_SSL3" && protos_to_try+=" -ssl3"

          for proto in $protos_to_try; do
               if [[ "$proto" == -tls1_1 ]]; then
                    num_bundles=1
                    bundle_size=$nr_ossl_ciphers
               fi
               for (( bundle=0; bundle < num_bundles; bundle++ )); do
                    end_of_bundle=$(( (bundle+1)*bundle_size ))
                    [[ $end_of_bundle -gt $nr_ossl_ciphers ]] && end_of_bundle=$nr_ossl_ciphers
                    while true; do
                         ciphers_to_test=""
                         tls13_ciphers_to_test=""
                         for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do
                              if ! "${ciphers_found2[i]}"; then
                                   if [[ "${ciph2[i]}" == TLS13* ]] || [[ "${ciph2[i]}" == TLS_* ]] || [[ "${ciph2[i]}" == AEAD-* ]]; then
                                        tls13_ciphers_to_test+=":${ciph2[i]}"
                                   else
                                        ciphers_to_test+=":${ciph2[i]}"
                                   fi
                              fi
                         done
                         [[ -z "$ciphers_to_test" ]] && [[ -z "$tls13_ciphers_to_test" ]] && break
                         $OPENSSL s_client $(s_client_options "$proto -cipher "\'${ciphers_to_test:1}\'" -ciphersuites "\'${tls13_ciphers_to_test:1}\'" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null
                         sclient_connect_successful $? "$TMPFILE" || break
                         cipher=$(get_cipher $TMPFILE)
                         [[ -z "$cipher" ]] && break
                         for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do
                              [[ "$cipher" == "${ciph2[i]}" ]] && ciphers_found2[i]=true && break
                         done
                         [[ $i -eq $end_of_bundle ]] && break
                         i=${index[i]}
                         ciphers_found[i]=true
                         if [[ "$cipher" == TLS13* ]] || [[ "$cipher" == TLS_* ]] || [[ "$cipher" == AEAD-* ]]; then
                              kx[i]="$(read_dhtype_from_file $TMPFILE)"
                         fi
                         if [[ ${kx[i]} == "Kx=ECDH" ]] || [[ ${kx[i]} == "Kx=DH" ]] || [[ ${kx[i]} == "Kx=EDH" ]]; then
                              dhlen=$(read_dhbits_from_file "$TMPFILE" quiet)
                              kx[i]="${kx[i]} $dhlen"
                         fi
                         "$SHOW_SIGALGO" && grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TMPFILE && \
                              sigalg[i]="$(read_sigalg_from_file "$TMPFILE")"
                    done
               done
          done

          if "$using_sockets"; then
               for (( i=0; i < nr_ciphers; i++ )); do
                    if ! "${ciphers_found[i]}" && [[ "${sslvers[i]}" != "SSLv2" ]]; then
                         ciphers_found2[nr_nonossl_ciphers]=false
                         hexcode2[nr_nonossl_ciphers]="${hexcode[i]}"
                         rfc_ciph2[nr_nonossl_ciphers]="${rfc_ciph[i]}"
                         index[nr_nonossl_ciphers]=$i
                         nr_nonossl_ciphers+=1
                    fi
               done
          fi

          if [[ $nr_nonossl_ciphers -eq 0 ]]; then
               num_bundles=0
          else
               # Some servers can't handle a handshake with >= 128 ciphers. So,
               # test cipher suites in bundles of 128 or less.
               num_bundles=$nr_nonossl_ciphers/128
               [[ $((nr_nonossl_ciphers%128)) -ne 0 ]] && num_bundles+=1

               bundle_size=$nr_nonossl_ciphers/$num_bundles
               [[ $((nr_nonossl_ciphers%num_bundles)) -ne 0 ]] && bundle_size+=1
          fi

          for proto in 04 03 02 01 00; do
               for (( bundle=0; bundle < num_bundles; bundle++ )); do
                    end_of_bundle=$(( (bundle+1)*bundle_size ))
                    [[ $end_of_bundle -gt $nr_nonossl_ciphers ]] && end_of_bundle=$nr_nonossl_ciphers
                    while true; do
                         ciphers_to_test=""
                         for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do
                              ! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode2[i]}"
                         done
                         [[ -z "$ciphers_to_test" ]] && break
                         [[ "$proto" == 04 ]] && [[ ! "$ciphers_to_test" =~ ,\ 13,[0-9a-f][0-9a-f] ]] && break
                         ciphers_to_test="$(strip_inconsistent_ciphers "$proto" "$ciphers_to_test")"
                         [[ -z "$ciphers_to_test" ]] && break
                         if "$SHOW_SIGALGO"; then
                              tls_sockets "$proto" "${ciphers_to_test:2}, 00,ff" "all"
                         else
                              tls_sockets "$proto" "${ciphers_to_test:2}, 00,ff" "ephemeralkey"
                         fi
                         sclient_success=$?
                         [[ $sclient_success -ne 0 ]] && [[ $sclient_success -ne 2 ]] && break
                         cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")
                         for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do
                              [[ "$cipher" == "${rfc_ciph2[i]}" ]] && ciphers_found2[i]=true && break
                         done
                         [[ $i -eq $end_of_bundle ]] && break
                         i=${index[i]}
                         ciphers_found[i]=true
                         [[ "${kx[i]}" == "Kx=any" ]] && kx[i]="$(read_dhtype_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")"
                         if [[ ${kx[i]} == "Kx=ECDH" ]] || [[ ${kx[i]} == "Kx=DH" ]] || [[ ${kx[i]} == "Kx=EDH" ]]; then
                              dhlen=$(read_dhbits_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" quiet)
                              kx[i]="${kx[i]} $dhlen"
                         fi
                         "$SHOW_SIGALGO" && [[ -r "$HOSTCERT" ]] && \
                              sigalg[i]="$(read_sigalg_from_file "$HOSTCERT")"
                    done
               done
          done

          for (( i=0; i < nr_ciphers; i++ )); do
               "${ciphers_found[i]}" || "$SHOW_EACH_C" || continue
               neat_list "${normalized_hexcode[i]}" "${ciph[i]}" "${kx[i]}" "${enc[i]}" "${export2[i]}" "${ciphers_found[i]}"
               available=""
               if "$SHOW_EACH_C"; then
                    if "${ciphers_found[i]}"; then
                         available="available"
                         pr_cyan "available"
                    else
                         available="not a/v"
                         pr_deemphasize "not a/v"
                    fi
               fi
               outln "${sigalg[i]}"
               fileout "cipher_${normalized_hexcode[i]}" "INFO" "$(neat_list "${normalized_hexcode[i]}" "${ciph[i]}" "${kx[i]}" "${enc[i]}" "${export2[i]}") $available"
          done
          "$using_sockets" && HAS_DH_BITS="$has_dh_bits"
          tmpfile_handle ${FUNCNAME[0]}.txt
     done
     outln

     tmpfile_handle ${FUNCNAME[0]}.txt
     return 0       # this is a single test for a cipher
}



# test for all ciphers locally configured (w/o distinguishing whether they are good or bad)
run_allciphers() {
     local -i nr_ciphers_tested=0 nr_ciphers=0 nr_ossl_ciphers=0 nr_nonossl_ciphers=0 sclient_success=0
     local n auth mac hexc sslv2_ciphers="" s
     local -a normalized_hexcode hexcode ciph sslvers kx enc export2 sigalg ossl_supported
     local -i i end_of_bundle bundle bundle_size num_bundles
     local -a ciphers_found ciphers_found2 hexcode2 ciph2 rfc_ciph2
     local -i -a index
     local proto protos_to_try
     local dhlen available ciphers_to_test tls13_ciphers_to_test supported_sslv2_ciphers
     local has_dh_bits="$HAS_DH_BITS"
     local using_sockets=true

     "$SSL_NATIVE" && using_sockets=false
     "$FAST" && using_sockets=false
     [[ $TLS_NR_CIPHERS == 0 ]] && using_sockets=false

     # get a list of all the cipher suites to test
     if "$using_sockets" || [[ $OSSL_VER_MAJOR -lt 1 ]]; then
          for (( i=0; i < TLS_NR_CIPHERS; i++ )); do
               hexc="$(tolower "${TLS_CIPHER_HEXCODE[i]}")"
               ciph[i]="${TLS_CIPHER_OSSL_NAME[i]}"
               sslvers[i]="${TLS_CIPHER_SSLVERS[i]}"
               kx[i]="${TLS_CIPHER_KX[i]}"
               enc[i]="${TLS_CIPHER_ENC[i]}"
               export2[i]="${TLS_CIPHER_EXPORT[i]}"
               ciphers_found[i]=false
               sigalg[i]=""
               ossl_supported[i]=${TLS_CIPHER_OSSL_SUPPORTED[i]}
               if "$using_sockets" && ! "$HAS_DH_BITS" && ( [[ ${kx[i]} == Kx=ECDH ]] || [[ ${kx[i]} == Kx=DH ]] || [[ ${kx[i]} == Kx=EDH ]] ); then
                    ossl_supported[i]=false
               fi
               if [[ ${#hexc} -eq 9 ]]; then
                    hexcode[i]="${hexc:2:2},${hexc:7:2}"
                    if [[ "${hexc:2:2}" == 00 ]]; then
                         normalized_hexcode[i]="x${hexc:7:2}"
                    else
                         normalized_hexcode[i]="x${hexc:2:2}${hexc:7:2}"
                    fi
               else
                    hexcode[i]="${hexc:2:2},${hexc:7:2},${hexc:12:2}"
                    normalized_hexcode[i]="x${hexc:2:2}${hexc:7:2}${hexc:12:2}"
                    sslv2_ciphers="$sslv2_ciphers, ${hexcode[i]}"
               fi
               if "$using_sockets" || "${TLS_CIPHER_OSSL_SUPPORTED[i]}"; then
                    nr_ciphers_tested+=1
               fi
          done
          nr_ciphers=$TLS_NR_CIPHERS
     else
          while read -r hexc n ciph[nr_ciphers] sslvers[nr_ciphers] kx[nr_ciphers] auth enc[nr_ciphers] mac export2[nr_ciphers]; do
               ciphers_found[nr_ciphers]=false
               if [[ ${#hexc} -eq 9 ]]; then
                    if [[ "${hexc:2:2}" == 00 ]]; then
                         normalized_hexcode[nr_ciphers]="$(tolower "x${hexc:7:2}")"
                    else
                         normalized_hexcode[nr_ciphers]="$(tolower "x${hexc:2:2}${hexc:7:2}")"
                    fi
               else
                    normalized_hexcode[nr_ciphers]="$(tolower "x${hexc:2:2}${hexc:7:2}${hexc:12:2}")"
               fi
               sigalg[nr_ciphers]=""
               ossl_supported[nr_ciphers]=true
               nr_ciphers=$nr_ciphers+1
          done < <(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 'ALL' "-V")
          nr_ciphers_tested=$nr_ciphers
     fi

     if "$using_sockets"; then
          sslv2_sockets "${sslv2_ciphers:2}" "true"
          if [[ $? -eq 3 ]] && [[ "$V2_HELLO_CIPHERSPEC_LENGTH" -ne 0 ]]; then
               supported_sslv2_ciphers="$(grep "Supported cipher: " "$TEMPDIR/$NODEIP.parse_sslv2_serverhello.txt")"
               "$SHOW_SIGALGO" && s="$(read_sigalg_from_file "$HOSTCERT")"
               for (( i=0 ; i<nr_ciphers; i++ )); do
                    if [[ "${sslvers[i]}" == SSLv2 ]] && [[ "$supported_sslv2_ciphers" =~ ${normalized_hexcode[i]} ]]; then
                         ciphers_found[i]=true
                         "$SHOW_SIGALGO" && sigalg[i]="$s"
                    fi
               done
          fi
     elif "$HAS_SSL2"; then
          $OPENSSL s_client $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY -ssl2 >$TMPFILE 2>$ERRFILE </dev/null
          sclient_connect_successful $? "$TMPFILE"
          if [[ $? -eq 0 ]]; then
               supported_sslv2_ciphers="$(grep -A 4 "Ciphers common between both SSL endpoints:" $TMPFILE)"
               "$SHOW_SIGALGO" && s="$(read_sigalg_from_file "$TMPFILE")"
               for (( i=0 ; i<nr_ciphers; i++ )); do
                    if [[ "${sslvers[i]}" == SSLv2 ]] && [[ "$supported_sslv2_ciphers" =~ ${ciph[i]} ]]; then
                         ciphers_found[i]=true
                         "$SHOW_SIGALGO" && sigalg[i]="$s"
                    fi
               done
          fi
     fi

     outln
     if "$using_sockets"; then
          pr_headlineln " Testing $nr_ciphers_tested ciphers via OpenSSL plus sockets against the server, ordered by encryption strength "
     else
          pr_headlineln " Testing all $nr_ciphers_tested locally available ciphers against the server, ordered by encryption strength "
          [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && pr_warning " Cipher mapping not available, doing a fallback to openssl"
          outln
          if ! "$HAS_DH_BITS"; then
               [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && out "."
               prln_warning " Your $OPENSSL cannot show DH/ECDH bits"
          fi
     fi
     outln
     neat_header

     for (( i=0; i < nr_ciphers; i++ )); do
          if "${ossl_supported[i]}"; then
               [[ "${sslvers[i]}" == SSLv2 ]] && continue
               ciphers_found2[nr_ossl_ciphers]=false
               ciph2[nr_ossl_ciphers]="${ciph[i]}"
               index[nr_ossl_ciphers]=$i
               nr_ossl_ciphers+=1
          fi
     done

     if [[ $nr_ossl_ciphers -eq 0 ]]; then
          num_bundles=0
     else
          # Some servers can't handle a handshake with >= 128 ciphers. So,
          # test cipher suites in bundles of 128 or less.
          num_bundles=$nr_ossl_ciphers/128
          [[ $((nr_ossl_ciphers%128)) -ne 0 ]] && num_bundles+=1

          bundle_size=$nr_ossl_ciphers/$num_bundles
          [[ $((nr_ossl_ciphers%num_bundles)) -ne 0 ]] && bundle_size+=1
     fi

     if "$HAS_TLS13"; then
          protos_to_try="-no_ssl2 -tls1_2 -tls1_1 -tls1"
     else
          protos_to_try="-no_ssl2 -tls1_1 -tls1"
     fi
     "$HAS_SSL3" && protos_to_try+=" -ssl3"

     for proto in $protos_to_try; do
          if [[ "$proto" == -tls1_1 ]]; then
               num_bundles=1
               bundle_size=$nr_ossl_ciphers
          fi

          [[ "$proto" != "-no_ssl2" ]] && [[ $(has_server_protocol "${proto:1}") -eq 1 ]] && continue
          for (( bundle=0; bundle < num_bundles; bundle++ )); do
               end_of_bundle=$(( (bundle+1)*bundle_size ))
               [[ $end_of_bundle -gt $nr_ossl_ciphers ]] && end_of_bundle=$nr_ossl_ciphers
               while true; do
                    ciphers_to_test=""
                    tls13_ciphers_to_test=""
                    for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do
                         if ! "${ciphers_found2[i]}"; then
                              if [[ "${ciph2[i]}" == TLS13* ]] || [[ "${ciph2[i]}" == TLS_* ]] || [[ "${ciph2[i]}" == AEAD-* ]]; then
                                   tls13_ciphers_to_test+=":${ciph2[i]}"
                              else
                                   ciphers_to_test+=":${ciph2[i]}"
                              fi
                         fi
                    done
                    [[ -z "$ciphers_to_test" ]] && [[ -z "$tls13_ciphers_to_test" ]] && break
                    $OPENSSL s_client $(s_client_options "$proto -cipher "\'${ciphers_to_test:1}\'" -ciphersuites "\'${tls13_ciphers_to_test:1}\'" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null
                    sclient_connect_successful $? "$TMPFILE" || break
                    cipher=$(get_cipher $TMPFILE)
                    [[ -z "$cipher" ]] && break
                    for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do
                         [[ "$cipher" == "${ciph2[i]}" ]] && ciphers_found2[i]=true && break
                    done
                    [[ $i -eq $end_of_bundle ]] && break
                    i=${index[i]}
                    ciphers_found[i]=true
                    if [[ "$cipher" == TLS13* ]] || [[ "$cipher" == TLS_* ]] || [[ "$cipher" == AEAD-* ]]; then
                         kx[i]="$(read_dhtype_from_file $TMPFILE)"
                    fi
                    if [[ ${kx[i]} == Kx=ECDH ]] || [[ ${kx[i]} == Kx=DH ]] || [[ ${kx[i]} == Kx=EDH ]]; then
                         dhlen=$(read_dhbits_from_file "$TMPFILE" quiet)
                         kx[i]="${kx[i]} $dhlen"
                    fi
                    "$SHOW_SIGALGO" && grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TMPFILE && \
                         sigalg[i]="$(read_sigalg_from_file "$TMPFILE")"
               done
          done
     done

     if "$using_sockets"; then
          for (( i=0; i < nr_ciphers; i++ )); do
               if ! "${ciphers_found[i]}"; then
                    [[ "${sslvers[i]}" == SSLv2 ]] && continue
                    ciphers_found2[nr_nonossl_ciphers]=false
                    hexcode2[nr_nonossl_ciphers]="${hexcode[i]}"
                    rfc_ciph2[nr_nonossl_ciphers]="${TLS_CIPHER_RFC_NAME[i]}"
                    index[nr_nonossl_ciphers]=$i
                    nr_nonossl_ciphers+=1
               fi
          done
     fi

     if [[ $nr_nonossl_ciphers -eq 0 ]]; then
          num_bundles=0
     else
          # Some servers can't handle a handshake with >= 128 ciphers. So,
          # test cipher suites in bundles of 128 or less.
          num_bundles=$nr_nonossl_ciphers/128
          [[ $((nr_nonossl_ciphers%128)) -ne 0 ]] && num_bundles+=1

          bundle_size=$nr_nonossl_ciphers/$num_bundles
          [[ $((nr_nonossl_ciphers%num_bundles)) -ne 0 ]] && bundle_size+=1
     fi

     for proto in 04 03 02 01 00; do
          for (( bundle=0; bundle < num_bundles; bundle++ )); do
               end_of_bundle=$(( (bundle+1)*bundle_size ))
               [[ $end_of_bundle -gt $nr_nonossl_ciphers ]] && end_of_bundle=$nr_nonossl_ciphers
               while true; do
                    ciphers_to_test=""
                    for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do
                         ! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode2[i]}"
                    done
                    [[ -z "$ciphers_to_test" ]] && break
                    [[ "$proto" == 04 ]] && [[ ! "$ciphers_to_test" =~ ,\ 13,[0-9a-f][0-9a-f] ]] && break
                    ciphers_to_test="$(strip_inconsistent_ciphers "$proto" "$ciphers_to_test")"
                    [[ -z "$ciphers_to_test" ]] && break
                    if "$SHOW_SIGALGO"; then
                         tls_sockets "$proto" "${ciphers_to_test:2}, 00,ff" "all"
                    else
                         tls_sockets "$proto" "${ciphers_to_test:2}, 00,ff" "ephemeralkey"
                    fi
                    sclient_success=$?
                    [[ $sclient_success -ne 0 ]] && [[ $sclient_success -ne 2 ]] && break
                    cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")
                    for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do
                         [[ "$cipher" == "${rfc_ciph2[i]}" ]] && ciphers_found2[i]=true && break
                    done
                    [[ $i -eq $end_of_bundle ]] && break
                    i=${index[i]}
                    ciphers_found[i]=true
                    [[ "${kx[i]}" == "Kx=any" ]] && kx[i]="$(read_dhtype_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")"
                    if [[ ${kx[i]} == "Kx=ECDH" ]] || [[ ${kx[i]} == "Kx=DH" ]] || [[ ${kx[i]} == "Kx=EDH" ]]; then
                         dhlen=$(read_dhbits_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" quiet)
                         kx[i]="${kx[i]} $dhlen"
                    fi
                    "$SHOW_SIGALGO" && [[ -r "$HOSTCERT" ]] && sigalg[i]="$(read_sigalg_from_file "$HOSTCERT")"
               done
          done
     done

     for (( i=0 ; i<nr_ciphers; i++ )); do
          if "${ciphers_found[i]}" || ( "$SHOW_EACH_C" && ( "$using_sockets" || "${ossl_supported[i]}" ) ); then
               neat_list "${normalized_hexcode[i]}" "${ciph[i]}" "${kx[i]}" "${enc[i]}" "${export2[i]}" "${ciphers_found[i]}"
               available=""
               if "$SHOW_EACH_C"; then
                    if ${ciphers_found[i]}; then
                         available="available"
                         pr_cyan "$available"
                    else
                         available="not a/v"
                         pr_deemphasize "$available"
                    fi
               fi
               outln "${sigalg[i]}"
               fileout "cipher_${normalized_hexcode[i]}" "INFO" "$(neat_list "${normalized_hexcode[i]}" "${ciph[i]}" "${kx[i]}" "${enc[i]}" "${export2[i]}") $available"
          fi
     done
     "$using_sockets" && HAS_DH_BITS="$has_dh_bits"

     outln
     [[ $sclient_success -ge 6 ]] && return 1
     return 0
}

# test for all ciphers per protocol locally configured (w/o distinguishing whether they are good or bad)
# for the specified protocol, test for all ciphers locally configured (w/o distinguishing whether they
# are good or bad) and list them in order to encryption strength.
ciphers_by_strength() {
     local proto="$1" proto_hex="$2" proto_text="$3"
     local using_sockets="$4" wide="$5"
     local ossl_ciphers_proto
     local -i nr_ciphers nr_ossl_ciphers nr_nonossl_ciphers success
     local n sslvers auth mac hexc sslv2_ciphers="" cipher
     local -a hexcode normalized_hexcode ciph rfc_ciph kx enc export2
     local -a hexcode2 ciph2 rfc_ciph2
     local -i i bundle end_of_bundle bundle_size num_bundles
     local -a ciphers_found ciphers_found2 sigalg ossl_supported index
     local dhlen supported_sslv2_ciphers ciphers_to_test tls13_ciphers_to_test addcmd temp
     local available
     local id
     local has_dh_bits="$HAS_DH_BITS"

     # for local problem if it happens
     "$wide" || out "  "
     if ! "$using_sockets" && ! locally_supported "$proto"; then
          pr_local_problem "Your $OPENSSL does not support $proto"
          "$wide" && outln
          return 0
     fi

     if [[ $(has_server_protocol "${proto:1}") -eq 1 ]]; then
          "$wide" && outln " - "
          return 0
     fi

     # get a list of all the cipher suites to test
     nr_ciphers=0
     if "$using_sockets" || [[ $OSSL_VER_MAJOR -lt 1 ]]; then
          for (( i=0; i < TLS_NR_CIPHERS; i++ )); do
               hexc="${TLS_CIPHER_HEXCODE[i]}"
               ciph[nr_ciphers]="${TLS_CIPHER_OSSL_NAME[i]}"
               rfc_ciph[nr_ciphers]="${TLS_CIPHER_RFC_NAME[i]}"
               kx[nr_ciphers]="${TLS_CIPHER_KX[i]}"
               enc[nr_ciphers]="${TLS_CIPHER_ENC[i]}"
               export2[nr_ciphers]="${TLS_CIPHER_EXPORT[i]}"
               ciphers_found[nr_ciphers]=false
               sigalg[nr_ciphers]=""
               ossl_supported[nr_ciphers]=${TLS_CIPHER_OSSL_SUPPORTED[i]}
               if "$using_sockets" && "$wide" && ! "$has_dh_bits" && \
                    ( [[ ${kx[nr_ciphers]} == "Kx=ECDH" ]] || [[ ${kx[nr_ciphers]} == "Kx=DH" ]] || [[ ${kx[nr_ciphers]} == "Kx=EDH" ]] ); then
                    ossl_supported[nr_ciphers]=false
               fi
               if [[ ${#hexc} -eq 9 ]]; then
                    hexcode[nr_ciphers]="${hexc:2:2},${hexc:7:2}"
                    if [[ "${hexc:2:2}" == 00 ]]; then
                         normalized_hexcode[nr_ciphers]="x${hexc:7:2}"
                    else
                         normalized_hexcode[nr_ciphers]="x${hexc:2:2}${hexc:7:2}"
                    fi
               else
                    hexc="$(tolower "$hexc")"
                    hexcode[nr_ciphers]="${hexc:2:2},${hexc:7:2},${hexc:12:2}"
                    normalized_hexcode[nr_ciphers]="x${hexc:2:2}${hexc:7:2}${hexc:12:2}"
               fi
               if ( "$using_sockets" || "${TLS_CIPHER_OSSL_SUPPORTED[i]}" ); then
                    if [[ ${#hexc} -eq 9 ]] && [[ "$proto" != -ssl2 ]]; then
                         if [[ "$proto" == -tls1_3 ]]; then
                              [[ "${hexc:2:2}" == 13 ]] && nr_ciphers+=1
                         elif [[ "$proto" == -tls1_2 ]]; then
                              [[ "${hexc:2:2}" != 13 ]] && nr_ciphers+=1
                         elif [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA256 ]] && [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA384 ]] && \
                              [[ "${TLS_CIPHER_RFC_NAME[i]}" != *_CCM ]] && [[ "${TLS_CIPHER_RFC_NAME[i]}" != *_CCM_8 ]]; then
                              nr_ciphers+=1
                         fi
                    elif [[ ${#hexc} -eq 14 ]] && [[ "$proto" == -ssl2 ]]; then
                         sslv2_ciphers+=", ${hexcode[nr_ciphers]}"
                         nr_ciphers+=1
                    fi
               fi
          done
     else # no sockets, openssl!
          # The OpenSSL ciphers function, prior to version 1.1.0, could only understand -ssl2, -ssl3, and -tls1.
          if [[ "$OSSL_NAME" =~ LibreSSL ]]; then
               ossl_ciphers_proto=""
          elif [[ $proto == -ssl2 ]] || [[ $proto == -ssl3 ]] || \
               [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 1.1.0* ]] || [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 1.1.1* ]] || \
               [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 3.0.0* ]]; then
               ossl_ciphers_proto="$proto"
          else
               ossl_ciphers_proto="-tls1"
          fi
          while read hexc n ciph[nr_ciphers] sslvers kx[nr_ciphers] auth enc[nr_ciphers] mac export2[nr_ciphers]; do
               if [[ "$proto" == -tls1_3 ]]; then
                    [[ "${ciph[nr_ciphers]}" == TLS13* ]] || [[ "${ciph[nr_ciphers]}" == TLS_* ]] || [[ "${ciph[nr_ciphers]}" == AEAD-* ]] || continue
               elif [[ "$proto" == -tls1_2 ]]; then
                    if [[ "${ciph[nr_ciphers]}" == TLS13* ]] || [[ "${ciph[nr_ciphers]}" == TLS_* ]] || [[ "${ciph[nr_ciphers]}" == AEAD-* ]]; then
                         continue
                    fi
               elif [[ "${ciph[nr_ciphers]}" == *-SHA256 ]] || [[ "${ciph[nr_ciphers]}" == *-SHA384 ]] || \
                    [[ "${ciph[nr_ciphers]}" == *-CCM ]] || [[ "${ciph[nr_ciphers]}" == *-CCM8 ]] || \
                    [[ "${ciph[nr_ciphers]}" =~ CHACHA20-POLY1305 ]]; then
                    continue
               fi
               ciphers_found[nr_ciphers]=false
               normalized_hexcode[nr_ciphers]="$(normalize_ciphercode "$hexc")"
               sigalg[nr_ciphers]=""
               ossl_supported[nr_ciphers]=true
               nr_ciphers+=1
          done < <(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 'ALL' "$ossl_ciphers_proto -V")
     fi

     if [[ $proto == -ssl2 ]]; then
          if "$using_sockets"; then
               sslv2_sockets "${sslv2_ciphers:2}" "true"
               if [[ $? -eq 3 ]] ; then
                    add_proto_offered ssl2 yes
                    if [[ "$V2_HELLO_CIPHERSPEC_LENGTH" -ne 0 ]]; then
                         supported_sslv2_ciphers="$(grep "Supported cipher: " "$TEMPDIR/$NODEIP.parse_sslv2_serverhello.txt")"
                         "$wide" && "$SHOW_SIGALGO" && s="$(read_sigalg_from_file "$HOSTCERT")"
                         for (( i=0 ; i<nr_ciphers; i++ )); do
                              if [[ "$supported_sslv2_ciphers" =~ ${normalized_hexcode[i]} ]]; then
                                   ciphers_found[i]=true
                                   "$wide" && "$SHOW_SIGALGO" && sigalg[i]="$s"
                              fi
                         done
                    else
                         outln " protocol supported with no cipher "
                    fi
               else
                    add_proto_offered ssl2 no
                    "$wide" && outln " - "
               fi
          else
               $OPENSSL s_client $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY -ssl2 >$TMPFILE 2>$ERRFILE </dev/null
               sclient_connect_successful $? "$TMPFILE"
               if [[ $? -eq 0 ]]; then
                    add_proto_offered ssl2 yes
                    supported_sslv2_ciphers="$(grep -A 4 "Ciphers common between both SSL endpoints:" $TMPFILE)"
                    "$wide" && "$SHOW_SIGALGO" && s="$(read_sigalg_from_file "$TMPFILE")"
                    for (( i=0 ; i<nr_ciphers; i++ )); do
                         if [[ "$supported_sslv2_ciphers" =~ ${ciph[i]} ]]; then
                              ciphers_found[i]=true
                              "$wide" && "$SHOW_SIGALGO" && sigalg[i]="$s"
                         fi
                    done
               else
                    add_proto_offered ssl2 no
                    "$wide" && outln " - "
               fi
          fi
     else # no SSLv2
          nr_ossl_ciphers=0
          if ( "$HAS_SSL3" || [[ $proto != -ssl3 ]] ) && ( "$HAS_TLS13" || [[ $proto != -tls1_3 ]] ); then
               for (( i=0; i < nr_ciphers; i++ )); do
                    if "${ossl_supported[i]}"; then
                         ciphers_found2[nr_ossl_ciphers]=false
                         ciph2[nr_ossl_ciphers]="${ciph[i]}"
                         index[nr_ossl_ciphers]=$i
                         nr_ossl_ciphers+=1
                    fi
               done
          fi
          if [[ $nr_ossl_ciphers -eq 0 ]]; then
               num_bundles=0
          else
               # Some servers can't handle a handshake with >= 128 ciphers. So,
               # test cipher suites in bundles of 128 or less.
               num_bundles=$nr_ossl_ciphers/128
               [[ $((nr_ossl_ciphers%128)) -ne 0 ]] && num_bundles+=1

               bundle_size=$nr_ossl_ciphers/$num_bundles
               [[ $((nr_ossl_ciphers%num_bundles)) -ne 0 ]] && bundle_size+=1
          fi

          for (( bundle=0; bundle < num_bundles; bundle++ )); do
               end_of_bundle=$(( (bundle+1)*bundle_size ))
               [[ $end_of_bundle -gt $nr_ossl_ciphers ]] && end_of_bundle=$nr_ossl_ciphers
               for (( success=0; success==0 ; 1 )); do
                    ciphers_to_test=""
                    tls13_ciphers_to_test=""
                    for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do
                         if ! "${ciphers_found2[i]}"; then
                              if [[ "$proto" == -tls1_3 ]]; then
                                   tls13_ciphers_to_test+=":${ciph2[i]}"
                              else
                                   ciphers_to_test+=":${ciph2[i]}"
                              fi
                         fi
                    done
                    success=1
                    if [[ -n "$ciphers_to_test" ]] || [[ -n "$tls13_ciphers_to_test" ]]; then
                         $OPENSSL s_client $(s_client_options "-cipher "\'${ciphers_to_test:1}\'" -ciphersuites "\'${tls13_ciphers_to_test:1}\'" $proto $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null
                         sclient_connect_successful $? "$TMPFILE"
                         if [[ $? -eq 0 ]]; then
                              cipher=$(get_cipher $TMPFILE)
                              if [[ -n "$cipher" ]]; then
                                   success=0
                                   for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do
                                        [[ "$cipher" == "${ciph2[i]}" ]] && ciphers_found2[i]=true && break
                                   done
                                   i=${index[i]}
                                   ciphers_found[i]=true
                                   "$wide" && [[ "$proto" == -tls1_3 ]] && kx[i]="$(read_dhtype_from_file $TMPFILE)"
                                   if "$wide" && ( [[ ${kx[i]} == Kx=ECDH ]] || [[ ${kx[i]} == Kx=DH ]] || [[ ${kx[i]} == Kx=EDH ]] ); then
                                        dhlen=$(read_dhbits_from_file "$TMPFILE" quiet)
                                        kx[i]="${kx[i]} $dhlen"
                                   fi
                                   "$wide" && "$SHOW_SIGALGO" && grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TMPFILE && \
                                        sigalg[i]="$(read_sigalg_from_file "$TMPFILE")"
                              fi
                         fi
                    fi
               done
          done

          if "$using_sockets"; then
               nr_nonossl_ciphers=0
               for (( i=0; i < nr_ciphers; i++ )); do
                    if ! "${ciphers_found[i]}"; then
                         ciphers_found2[nr_nonossl_ciphers]=false
                         hexcode2[nr_nonossl_ciphers]="${hexcode[i]}"
                         rfc_ciph2[nr_nonossl_ciphers]="${rfc_ciph[i]}"
                         index[nr_nonossl_ciphers]=$i
                         nr_nonossl_ciphers+=1
                    fi
               done
          fi

          if [[ $nr_nonossl_ciphers -eq 0 ]]; then
               num_bundles=0
          else
               # Some servers can't handle a handshake with >= 128 ciphers. So,
               # test cipher suites in bundles of 128 or less.
               num_bundles=$nr_nonossl_ciphers/128
               [[ $((nr_nonossl_ciphers%128)) -ne 0 ]] && num_bundles+=1

               bundle_size=$nr_nonossl_ciphers/$num_bundles
               [[ $((nr_nonossl_ciphers%num_bundles)) -ne 0 ]] && bundle_size+=1
          fi

          for (( bundle=0; bundle < num_bundles; bundle++ )); do
               end_of_bundle=$(( (bundle+1)*bundle_size ))
               [[ $end_of_bundle -gt $nr_nonossl_ciphers ]] && end_of_bundle=$nr_nonossl_ciphers
               for (( success=0; success==0 ; 1 )); do
                    ciphers_to_test=""
                    for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do
                         ! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode2[i]}"
                    done
                    success=1
                    if [[ -n "$ciphers_to_test" ]]; then
                         if "$wide" && "$SHOW_SIGALGO"; then
                              tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "all"
                         else
                              tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "ephemeralkey"
                         fi
                         if [[ $? -eq 0 ]]; then
                              success=0
                              cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")
                              for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do
                                   [[ "$cipher" == "${rfc_ciph2[i]}" ]] && ciphers_found2[i]=true && break
                              done
                              i=${index[i]}
                              ciphers_found[i]=true
                              "$wide" && [[ "$proto" == -tls1_3 ]] && kx[i]="$(read_dhtype_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")"
                              if "$wide" && ( [[ ${kx[i]} == Kx=ECDH ]] || [[ ${kx[i]} == Kx=DH ]] || [[ ${kx[i]} == Kx=EDH ]] ); then
                                   dhlen=$(read_dhbits_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" quiet)
                                   kx[i]="${kx[i]} $dhlen"
                              fi
                              "$wide" && "$SHOW_SIGALGO" && [[ -r "$HOSTCERT" ]] && \
                                   sigalg[i]="$(read_sigalg_from_file "$HOSTCERT")"
                         fi
                    fi
               done
          done
     fi

     cipher=""
     for (( i=0 ; i<nr_ciphers; i++ )); do
          if "${ciphers_found[i]}"; then
               if [[ "$DISPLAY_CIPHERNAMES" =~ rfc ]] && [[ "${rfc_ciph[i]}" != - ]]; then
                    cipher+="${rfc_ciph[i]} "
               else
                    cipher+="${ciph[i]} "
               fi
          fi
          if "$wide" && ( "${ciphers_found[i]}" || "$SHOW_EACH_C" ); then
               normalized_hexcode[i]="$(tolower "${normalized_hexcode[i]}")"
               neat_list "${normalized_hexcode[i]}" "${ciph[i]}" "${kx[i]}" "${enc[i]}" "${export2[i]}" "${ciphers_found[i]}"
               available=""
               if "$SHOW_EACH_C"; then
                    if "${ciphers_found[i]}"; then
                         available="available"
                         pr_cyan "$available"
                    else
                         available="not a/v"
                         pr_deemphasize "$available"
                    fi
               fi
               outln "${sigalg[i]}"
               id="cipher$proto"
               id+="_${normalized_hexcode[i]}"
               fileout "$id" "INFO" "$proto_text  $(neat_list "${normalized_hexcode[i]}" "${ciph[i]}" "${kx[i]}" "${enc[i]}" "${export2[i]}") $available"
          fi
     done

     if [[ $proto != -ssl2 ]]; then
          # We handled SSLv2 above already
          if [[ -n "$cipher" ]]; then
               add_proto_offered $proto yes
          else
               add_proto_offered $proto no
               "$wide" && outln " -"
          fi
     fi
     if ! "$wide" && [[ -n "$cipher" ]]; then
          outln
          out "$(printf "    %-10s " "$proto_text: ")"
          if [[ "$COLOR" -le 2 ]]; then
               out "$(out_row_aligned_max_width "$cipher" "               " $TERM_WIDTH)"
          else
               out_row_aligned_max_width_by_entry "$cipher" "               " $TERM_WIDTH pr_cipher_quality
          fi
     fi
     [[ -n "$cipher" ]] && fileout "supportedciphers_${proto_text//./_}" "INFO" "$cipher"

     "$using_sockets" && HAS_DH_BITS="$has_dh_bits"
     tmpfile_handle ${FUNCNAME[0]}${proto}.txt
     return 0
#FIXME: no error condition
}

# test for all ciphers per protocol locally configured (w/o distinguishing whether they are good or bad)
run_cipher_per_proto() {
     local proto proto_hex proto_text
     local using_sockets=true

     "$SSL_NATIVE" && using_sockets=false
     "$FAST" && using_sockets=false
     [[ $TLS_NR_CIPHERS == 0 ]] && using_sockets=false

     outln
     if "$using_sockets"; then
          pr_headlineln " Testing ciphers per protocol via OpenSSL plus sockets against the server, ordered by encryption strength "
     else
          pr_headlineln " Testing all locally available ciphers per protocol against the server, ordered by encryption strength "
          [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && pr_warning " Cipher mapping not available, doing a fallback to openssl"
          outln
          if ! "$HAS_DH_BITS"; then
               [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && out "."
               prln_warning "    (Your $OPENSSL cannot show DH/ECDH bits)"
          fi
     fi
     outln
     neat_header
     while read proto proto_hex proto_text; do
          prln_underline "$(printf -- "%b" "$proto_text")"
          ciphers_by_strength "$proto" "$proto_hex" "$proto_text" "$using_sockets" "true"
     done <<< "$(tm_out " -ssl2 22 SSLv2\n -ssl3 00 SSLv3\n -tls1 01 TLS 1\n -tls1_1 02 TLS 1.1\n -tls1_2 03 TLS 1.2\n -tls1_3 04 TLS 1.3")"
     return 0
#FIXME: no error condition
}

# arg1 is an ASCII-HEX encoded SSLv3 or TLS ClientHello.
# arg2: new key_share extension (only present to response to HelloRetryRequest)
# arg3: cookie extension (if needed for response to HelloRetryRequest)
#
# This function may be used to either modify a ClientHello for client simulation
# or to create a second ClientHello in response to a HelloRetryRequest.
# If arg2 is present, then this is a response to a HelloRetryRequest, so the
# function replaces the key_share extension with arg2 and adds the cookie
# extension, if present.
# If arg2 is not present, then this is an initial ClientHello for client simulation.
# In this case, if the provided ClientHello contains a server name extension,
# then either:
#  1) replace it with one corresponding to $SNI; or
#  2) remove it, if $SNI is empty
modify_clienthello() {
     local tls_handshake_ascii="$1"
     local new_key_share="$2" cookie="$3"
     local -i len offset tls_handshake_ascii_len len_all len_clienthello
     local -i len_extensions len_extension
     local tls_content_type tls_version_reclayer handshake_msg_type tls_clientversion
     local tls_random tls_sid tls_cipher_suites tls_compression_methods
     local tls_extensions="" extension_type len_extensions_hex
     local len_servername hexdump_format_str servername_hexstr
     local len_servername_hex len_sni_listlen len_sni_ext
     local tls_client_hello len_clienthello_hex tls_handshake_ascii_len_hex
     local sni_extension_found=false

     tls_handshake_ascii_len=${#tls_handshake_ascii}

     tls_content_type="${tls_handshake_ascii:0:2}"
     tls_version_reclayer="${tls_handshake_ascii:2:4}"
     len_all=$(hex2dec "${tls_handshake_ascii:6:4}")

     handshake_msg_type="${tls_handshake_ascii:10:2}"
     len_clienthello=$(hex2dec "${tls_handshake_ascii:12:6}")
     tls_clientversion="${tls_handshake_ascii:18:4}"
     tls_random="${tls_handshake_ascii:22:64}"
     len=2*$(hex2dec "${tls_handshake_ascii:86:2}")+2
     tls_sid="${tls_handshake_ascii:86:$len}"
     offset=86+$len

     len=2*$(hex2dec "${tls_handshake_ascii:$offset:4}")+4
     tls_cipher_suites="${tls_handshake_ascii:$offset:$len}"
     offset=$offset+$len

     len=2*$(hex2dec "${tls_handshake_ascii:$offset:2}")+2
     tls_compression_methods="${tls_handshake_ascii:$offset:$len}"
     offset=$offset+$len

     if [[ $offset -ge $tls_handshake_ascii_len ]]; then
          # No extensions
          tm_out "$tls_handshake_ascii"
          return 0
     fi

     len_extensions=2*$(hex2dec "${tls_handshake_ascii:$offset:4}")
     offset+=4
     for (( 1; offset < tls_handshake_ascii_len; 1 )); do
          extension_type="${tls_handshake_ascii:$offset:4}"
          offset+=4
          len_extension=2*$(hex2dec "${tls_handshake_ascii:$offset:4}")

          if [[ "$extension_type" == 0000 ]] && [[ -z "$new_key_share" ]]; then
               # If this is an initial ClientHello, then either remove
               # the SNI extension or replace it with the correct server name.
               sni_extension_found=true
               if [[ -n "$SNI" ]]; then
                    # Create a server name extension that corresponds to $SNI
                    len_servername=${#NODE}
                    hexdump_format_str="$len_servername/1 \"%02x\""
                    servername_hexstr=$(printf $NODE | hexdump -v -e "${hexdump_format_str}")
                    # convert lengths we need to fill in from dec to hex:
                    len_servername_hex=$(printf "%02x\n" $len_servername)
                    len_sni_listlen=$(printf "%02x\n" $((len_servername+3)))
                    len_sni_ext=$(printf "%02x\n" $((len_servername+5)))
                    tls_extensions+="000000${len_sni_ext}00${len_sni_listlen}0000${len_servername_hex}${servername_hexstr}"
               fi
               offset+=$len_extension+4
          elif [[ "$extension_type" != 00$KEY_SHARE_EXTN_NR ]] || [[ -z "$new_key_share" ]]; then
               # If this is in response to a HelloRetryRequest, then do
               # not copy over the old key_share extension, but
               # all other extensions should be copied into the new ClientHello.
               offset=$offset-4
               len=$len_extension+8
               tls_extensions+="${tls_handshake_ascii:$offset:$len}"
               offset+=$len
          else
               # This is the key_share extension, and the modified ClientHello
               # is being created in response to a HelloRetryRequest. Replace
               # the existing key_share extension with the new one.
               tls_extensions+="$new_key_share"
               offset+=$len_extension+4
          fi
     done
     tls_extensions+="$cookie"

     if ! "$sni_extension_found" && [[ -z "$new_key_share" ]]; then
          tm_out "$tls_handshake_ascii"
          return 0
     fi

     len_extensions=${#tls_extensions}/2
     len_extensions_hex=$(printf "%02x\n" $len_extensions)
     len2twobytes "$len_extensions_hex"
     tls_extensions="${LEN_STR:0:2}${LEN_STR:4:2}${tls_extensions}"

     tls_client_hello="${tls_clientversion}${tls_random}${tls_sid}${tls_cipher_suites}${tls_compression_methods}${tls_extensions}"
     len_clienthello=${#tls_client_hello}/2
     len_clienthello_hex=$(printf "%02x\n" $len_clienthello)
     len2twobytes "$len_clienthello_hex"
     tls_handshake_ascii="${handshake_msg_type}00${LEN_STR:0:2}${LEN_STR:4:2}${tls_client_hello}"

     tls_handshake_ascii_len=${#tls_handshake_ascii}/2
     tls_handshake_ascii_len_hex=$(printf "%02x\n" $tls_handshake_ascii_len)
     len2twobytes "$tls_handshake_ascii_len_hex"
     tls_handshake_ascii="${tls_content_type}${tls_version_reclayer}${LEN_STR:0:2}${LEN_STR:4:2}${tls_handshake_ascii}"
     tm_out "$tls_handshake_ascii"
     return 0
}

client_simulation_sockets() {
     local -i len i ret=0
     local -i save=0
     local lines clienthello data=""
     local cipher_list_2send=""
     local sock_reply_file2 sock_reply_file3
     local tls_hello_ascii next_packet hello_done=0
     local -i sid_len offset1 offset2

     if [[ "${1:0:4}" == 1603 ]]; then
          clienthello="$(modify_clienthello "$1")"
          TLS_CLIENT_HELLO="${clienthello:10}"
     else
          clienthello="$1"
          TLS_CLIENT_HELLO=""
     fi
     len=${#clienthello}
     for (( i=0; i < len; i+=2 )); do
          data+=", ${clienthello:i:2}"
     done
     # same as above. If a CIPHER_SUITES string was provided, then check that it is in the ServerHello
     # this appeared 1st in yassl + MySQL (https://github.com/drwetter/testssl.sh/pull/784) but adds
     # robustness to the implementation
     # see also https://github.com/drwetter/testssl.sh/pull/797
     if [[ "${1:0:4}" == 1603 ]]; then
          # Extact list of cipher suites from SSLv3 or later ClientHello
          sid_len=4*$(hex2dec "${data:174:2}")
          offset1=178+$sid_len
          offset2=182+$sid_len
          len=4*$(hex2dec "${data:offset1:2}${data:offset2:2}")-2
          offset1=186+$sid_len
          code2network "$(tolower "${data:offset1:len}")"    # convert CIPHER_SUITES to a "standardized" format
     else
          # Extact list of cipher suites from SSLv2 ClientHello
          len=2*$(hex2dec "${clienthello:12:2}")
          for (( i=22; i < 22+len; i+=6 )); do
               offset1=$i+2
               offset2=$i+4
               [[ "${clienthello:i:2}" == 00 ]] && cipher_list_2send+=", ${clienthello:offset1:2},${clienthello:offset2:2}"
          done
          code2network "$(tolower "${cipher_list_2send:2}")" # convert CIPHER_SUITES to a "standardized" format
     fi
     cipher_list_2send="$NW_STR"

     fd_socket 5 || return 6
     debugme echo -e "\nsending client hello... "
     socksend_clienthello "${data}"
     sleep $USLEEP_SND

     sockread_serverhello 32768
     tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE")
     tls_hello_ascii="${tls_hello_ascii%%[!0-9A-F]*}"

     # Check if the response is a HelloRetryRequest.
     resend_if_hello_retry_request "$clienthello" "$tls_hello_ascii"
     ret=$?
     if [[ $ret -eq 2 ]]; then
          tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE")
          tls_hello_ascii="${tls_hello_ascii%%[!0-9A-F]*}"
     elif [[ $ret -eq 1 ]] || [[ $ret -eq 6 ]]; then
          close_socket
          TMPFILE=$SOCK_REPLY_FILE
          tmpfile_handle ${FUNCNAME[0]}.dd
          return $ret
     fi

     if [[ "${tls_hello_ascii:0:1}" != "8" ]]; then
          check_tls_serverhellodone "$tls_hello_ascii" "ephemeralkey"
          hello_done=$?
     fi

     for(( 1 ; hello_done==1; 1 )); do
          if [[ $DEBUG -ge 1 ]]; then
               sock_reply_file2=${SOCK_REPLY_FILE}.2
               mv "$SOCK_REPLY_FILE" "$sock_reply_file2"
          fi

          debugme echo -n "requesting more server hello data... "
          socksend "" $USLEEP_SND
          sockread_serverhello 32768

          next_packet=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE")
          next_packet="${next_packet%%[!0-9A-F]*}"
          if [[ ${#next_packet} -eq 0 ]]; then
               # This shouldn't be necessary. However, it protects against
               # getting into an infinite loop if the server has nothing
               # left to send and check_tls_serverhellodone doesn't
               # correctly catch it.
               [[ $DEBUG -ge 1 ]] && mv "$sock_reply_file2" "$SOCK_REPLY_FILE"
               hello_done=0
          else
               tls_hello_ascii+="$next_packet"
               if [[ $DEBUG -ge 1 ]]; then
                    sock_reply_file3=${SOCK_REPLY_FILE}.3
                    mv "$SOCK_REPLY_FILE" "$sock_reply_file3"    #FIXME: we moved that already
                    mv "$sock_reply_file2" "$SOCK_REPLY_FILE"
                    cat "$sock_reply_file3" >> "$SOCK_REPLY_FILE"
                    rm "$sock_reply_file3"
               fi

               check_tls_serverhellodone "$tls_hello_ascii" "ephemeralkey"
               hello_done=$?
          fi
     done

     debugme echo "reading server hello..."
     if [[ "$DEBUG" -ge 4 ]]; then
          hexdump -C $SOCK_REPLY_FILE | head -6
          echo
     fi
     if [[ "${tls_hello_ascii:0:1}" == 8 ]]; then
          parse_sslv2_serverhello "$SOCK_REPLY_FILE" "false"
          if [[ $? -eq 3 ]] && [[ "$V2_HELLO_CIPHERSPEC_LENGTH" -ne 0 ]]; then
               echo "Protocol  : SSLv2" > "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt"
               DETECTED_TLS_VERSION="0200"
               ret=0
          else
               ret=1
          fi
     else
          parse_tls_serverhello "$tls_hello_ascii" "ephemeralkey" "$cipher_list_2send"
          save=$?

          if [[ $save -eq 0 ]]; then
               send_close_notify "$DETECTED_TLS_VERSION"
          fi

          if [[ $DEBUG -ge 2 ]]; then
               # see https://secure.wand.net.nz/trac/libprotoident/wiki/SSL
               lines=$(count_lines "$(hexdump -C "$SOCK_REPLY_FILE" 2>$ERRFILE)")
               tm_out "  ($lines lines returned)  "
          fi

          # determine the return value for higher level, so that they can tell what the result is
          if [[ $save -eq 1 ]] || [[ $lines -eq 1 ]]; then
               ret=1          # NOT available
          else
               ret=0
          fi
          debugme tmln_out
     fi

     close_socket
     TMPFILE=$SOCK_REPLY_FILE
     tmpfile_handle ${FUNCNAME[0]}.dd
     return $ret
}

run_client_simulation() {
     # Runs browser simulations. Browser capabilities gathered from:
     # https://www.ssllabs.com/ssltest/clients.html on 10 jan 2016
     local names=()
     local short=()
     local protos=()
     local ch_ciphers=()
     local ciphersuites=()
     local tlsvers=()
     local ch_sni=()
     local warning=()
     local handshakebytes=()
     local lowest_protocol=()
     local highest_protocol=()
     local service=()
     local minDhBits=()
     local maxDhBits=()
     local minRsaBits=()
     local maxRsaBits=()
     local minEcdsaBits=()
     local curves=()
     local requiresSha2=()
     local current=()
     local i=0
     local name tls proto cipher temp what_dh bits curve supported_curves
     local has_dh_bits using_sockets=true
     local client_service
     local options
     local -i ret=0
     local jsonID="clientsimulation"
     local client_service=""

     # source the external file
     . "$TESTSSL_INSTALL_DIR/etc/client-simulation.txt" 2>/dev/null
     if [[ $? -ne 0 ]]; then
          prln_local_problem "couldn't find client simulation data in $TESTSSL_INSTALL_DIR/etc/client-simulation.txt"
          return 1
     fi

     "$SSL_NATIVE" && using_sockets=false

     if [[ $SERVICE != "" ]];  then
          client_service="$SERVICE"
     elif [[ -n "$STARTTLS_PROTOCOL" ]]; then
          # Can we take the service from STARTTLS?
          client_service=$(toupper "${STARTTLS_PROTOCOL%s}")    # strip trailing 's' in ftp(s), smtp(s), pop3(s), etc
     elif "$ASSUME_HTTP"; then
          client_service="HTTP"
     else
          outln "Could not determine the protocol, only simulating generic clients."
     fi

     outln
     pr_headline " Running client simulations "
     [[ "$client_service" == HTTP ]] && pr_headline "($client_service) "
     if "$using_sockets"; then
          pr_headlineln "via sockets "
     else
          pr_headline "via openssl "
          prln_warning " -- pls note \"--ssl-native\" will return some false results"
          fileout "$jsonID" "WARN" "You shouldn't run this with \"--ssl-native\" as you will get false results"
          ret=1
     fi
     outln
     debugme echo

     if "$WIDE"; then
          if [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]]; then
               out " Browser                      Protocol  Cipher Suite Name (OpenSSL)       "
               ( "$using_sockets" || "$HAS_DH_BITS") && out "Forward Secrecy"
               outln
               out "--------------------------------------------------------------------------"
          else
               out " Browser                      Protocol  Cipher Suite Name (IANA/RFC)                      "
               ( "$using_sockets" || "$HAS_DH_BITS") && out "Forward Secrecy"
               outln
               out "------------------------------------------------------------------------------------------"
          fi
          ( "$using_sockets" || "$HAS_DH_BITS") && out "----------------------"
          outln
     fi
     if ! "$using_sockets"; then
          # We can't use the connectivity checker here as of now the openssl reply is always empty (reason??)
          save_max_ossl_fail=$MAX_OSSL_FAIL
          nr_ossl_fail=$NR_OSSL_FAIL
          MAX_OSSL_FAIL=100
     fi
     for name in "${short[@]}"; do
          if "${current[i]}" || "$ALL_CLIENTS" ; then
               # for ANY we test this service or if the service we determined from STARTTLS matches
               if [[ "${service[i]}" == ANY ]] || [[ "${service[i]}" =~ $client_service ]]; then
                    out " $(printf -- "%-29s" "${names[i]}")"
                    if "$using_sockets" && [[ -n "${handshakebytes[i]}" ]]; then
                         client_simulation_sockets "${handshakebytes[i]}"
                         sclient_success=$?
                         if [[ $sclient_success -eq 0 ]]; then
                              if [[ "0x${DETECTED_TLS_VERSION}" -lt ${lowest_protocol[i]} ]] || \
                                 [[ "0x${DETECTED_TLS_VERSION}" -gt ${highest_protocol[i]} ]]; then
                                   sclient_success=1
                              fi
                              [[ $sclient_success -eq 0 ]] && cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" $TMPFILE >$ERRFILE
                         fi
                    else
                         if [[ -n "${curves[i]}" ]]; then
                              # "$OPENSSL s_client" will fail if the -curves option includes any unsupported curves.
                              supported_curves=""
                              for curve in $(colon_to_spaces "${curves[i]}"); do
                                   # Attention! secp256r1 = prime256v1 and secp192r1 = prime192v1
                                   # We need to map two curves here as otherwise handshakes will go wrong if "-curves" are supplied
                                   # https://github.com/openssl/openssl/blob/master/apps/ecparam.c#L221 + ./ssl/t1_lib.c
                                   [[ "$curve" =~ secp256r1 ]] && curve="${curve//secp256r1/prime256v1}"
                                   [[ "$curve" =~ secp192r1 ]] && curve="${curve//secp192r1/prime192v1}"
                                   [[ "$OSSL_SUPPORTED_CURVES" =~ " $curve " ]] && supported_curves+=":$curve"
                              done
                              curves[i]=""
                              [[ -n "$supported_curves" ]] && curves[i]="-curves ${supported_curves:1}"
                         fi
                         options="$(s_client_options "-cipher ${ch_ciphers[i]} -ciphersuites "\'${ciphersuites[i]}\'" ${curves[i]} ${protos[i]} $STARTTLS $BUGS $PROXY -connect $NODEIP:$PORT ${ch_sni[i]}")"
                         debugme echo "$OPENSSL s_client $options  </dev/null"
                         $OPENSSL s_client $options </dev/null >$TMPFILE 2>$ERRFILE
                         sclient_connect_successful $? $TMPFILE
                         sclient_success=$?
                    fi
                    if [[ $sclient_success -eq 0 ]]; then
                         # If an ephemeral DH key was used, check that the number of bits is within range.
                         temp=$(awk -F': ' '/^Server Temp Key/ { print $2 }' "$TMPFILE")        # extract line
                         what_dh="${temp%%,*}"
                         bits="${temp##*, }"
                         # formatting
                         curve="${temp#*, }"
                         if [[ "$curve" == $bits ]]; then
                              curve=""
                         else
                              curve="${curve%%,*}"
                         fi
                         bits="${bits/bits/}"
                         bits="${bits// /}"
                         if [[ "$what_dh" == X25519 ]] || [[ "$what_dh" == X448 ]]; then
                              curve="$what_dh"
                              what_dh="ECDH"
                         fi
                         if [[ "$what_dh" == DH ]]; then
                              [[ ${minDhBits[i]} -ne -1 ]] && [[ $bits -lt ${minDhBits[i]} ]] && sclient_success=1
                              [[ ${maxDhBits[i]} -ne -1 ]] && [[ $bits -gt ${maxDhBits[i]} ]] && sclient_success=1
                         fi
                    fi
                    if [[ $sclient_success -ne 0 ]]; then
                         outln "No connection"
                         fileout "${jsonID}-${short[i]}" "INFO" "No connection"
                    else
                         proto=$(get_protocol $TMPFILE)
                         # hack:
                         [[ "$proto" == TLSv1 ]] && proto="TLSv1.0"
                         [[ "$proto" == SSLv3 ]] && proto="SSLv3  "
                         if [[ "$proto" == TLSv1.2 ]] && ( ! "$using_sockets" || [[ -z "${handshakebytes[i]}" ]] ); then
                              # OpenSSL reports TLS1.2 even if the connection is TLS1.1 or TLS1.0. Need to figure out which one it is...
                              for tls in ${tlsvers[i]}; do
                                   # If the handshake data includes TLS 1.3 we need to remove it, otherwise the
                                   # simulation will fail with # 'Oops: openssl s_client connect problem'
                                   # before/after trying another protocol. We only print a warning it in debug mode
                                   # as otherwise we would need e.g. handle the curves in a similar fashion -- not
                                   # to speak about ciphers
                                   if [[ $tls =~ 1_3 ]] && ! "$HAS_TLS13"; then
                                        debugme pr_local_problem "TLS 1.3 not supported, "
                                        continue
                                   fi
                                   options="$(s_client_options "$tls -cipher ${ch_ciphers[i]} -ciphersuites "\'${ciphersuites[i]}\'" ${curves[i]} $STARTTLS $BUGS $PROXY -connect $NODEIP:$PORT ${ch_sni[i]}")"
                                   debugme echo "$OPENSSL s_client $options  </dev/null"
                                   $OPENSSL s_client $options  </dev/null >$TMPFILE 2>$ERRFILE
                                   sclient_connect_successful $? $TMPFILE
                                   sclient_success=$?
                                   if [[ $sclient_success -eq 0 ]]; then
                                        case "$tls" in
                                             "-tls1_2") break ;;
                                             "-tls1_1") proto="TLSv1.1"
                                                        break ;;
                                             "-tls1")   proto="TLSv1.0"
                                                        break ;;
                                        esac
                                   fi
                              done
                         fi
                         cipher=$(get_cipher $TMPFILE)
                         if [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]] && ( [[ "$cipher" == TLS_* ]] || [[ "$cipher" == SSL_* ]] ); then
                              cipher="$(rfc2openssl "$cipher")"
                              [[ -z "$cipher" ]] && cipher=$(get_cipher $TMPFILE)
                         elif [[ "$DISPLAY_CIPHERNAMES" =~ rfc ]] && [[ "$cipher" != TLS_* ]] && [[ "$cipher" != SSL_* ]]; then
                              cipher="$(openssl2rfc "$cipher")"
                              [[ -z "$cipher" ]] && cipher=$(get_cipher $TMPFILE)
                         fi
                         out "$proto "
                         "$WIDE" && out "  "
                         if [[ "$COLOR" -le 2 ]]; then
                              out "$cipher"
                         else
                              pr_cipher_quality "$cipher"
                         fi
                         if "$WIDE"; then
                              if [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]]; then
                                   for (( j=${#cipher}; j < 34; j++ )); do
                                        out " "
                                   done
                              else
                                   for (( j=${#cipher}; j < 50; j++ )); do
                                        out " "
                                   done
                              fi
                         fi
                         if [[ -n "$what_dh" ]]; then
                              [[ -n "$curve" ]] && curve="($curve)"
                              "$WIDE" || out ", "
                              if [[ "$what_dh" == ECDH ]]; then
                                   pr_ecdh_quality "$bits" "$(printf -- "%-12s" "$bits bit $what_dh") $curve"
                              else
                                   pr_dh_quality "$bits" "$(printf -- "%-12s" "$bits bit $what_dh") $curve"
                              fi
                         else
                              if "$HAS_DH_BITS" || ( "$using_sockets" && [[ -n "${handshakebytes[i]}" ]] ); then
                                   "$WIDE" || out ", "
                                   out "No FS"
                              fi
                         fi
                         outln
                         if [[ -n "${warning[i]}" ]]; then
                              out "                            "
                              outln "${warning[i]}"
                         fi
                         fileout "${jsonID}-${short[i]}" "INFO" "$proto $cipher  ${warning[i]}"
                         debugme cat $TMPFILE
                    fi
               fi   # correct service?
          fi   #current?
          ((i++))
     done
     if ! "$using_sockets"; then
          # restore from above
          MAX_OSSL_FAIL=$save_max_ossl_fail
          NR_OSSL_FAIL=$nr_ossl_fail
     fi

     tmpfile_handle ${FUNCNAME[0]}.txt
     return $ret
}

# generic function whether $1 is supported by s_client ($2: string to display, currently nowhere being used)
#
locally_supported() {
     [[ -n "$2" ]] && out "$2 "
     if $OPENSSL s_client "$1" -connect invalid. 2>&1 | grep -aiq "unknown option"; then
          prln_local_problem "$OPENSSL doesn't support \"s_client $1\""
          return 7
     fi
     return 0
}


# The protocol check in run_protocols needs to be redone. The using_sockets part there kind of sucks.
# 1) we need to have a variable where the results are being stored so that every other test doesn't have to do this again
#   --> we have that but certain information like "downgraded" are not being passed. That's not ok for run_protocols()/
#   for all other functions we can use it
# 2) the code is old and one can do that way better
# We should do what's available and faster (openssl vs. sockets). Keep in mind that the socket reply for SSLv2 returns the number # of ciphers!
#
# arg1: -ssl2|-ssl3|-tls1|-tls1_1|-tls1_2|-tls1_3
#
run_prototest_openssl() {
     local -i ret=0
     local protos proto

     # check whether the protocol being tested is supported by $OPENSSL
     $OPENSSL s_client "$1" -connect invalid. 2>&1 | grep -aiq "unknown option" && return 7
     case "$1" in
          -ssl2) protos="-ssl2" ;;
          -ssl3) protos="-ssl3" ;;
          -tls1) protos="-no_tls1_2 -no_tls1_1 -no_ssl2"; "$HAS_TLS13" && protos+=" -no_tls1_3" ;;
          -tls1_1) protos="-no_tls1_2 -no_ssl2"; "$HAS_TLS13" && protos+=" -no_tls1_3" ;;
          -tls1_2) protos="-no_ssl2"; "$HAS_TLS13" && protos+=" -no_tls1_3" ;;
          -tls1_3) protos="" ;;
     esac
     $OPENSSL s_client $(s_client_options "-state $protos $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>&1 </dev/null
     sclient_connect_successful $? $TMPFILE
     ret=$?
     debugme grep -E "error|failure" $ERRFILE | grep -Eav "unable to get local|verify error"
     if [[ $ret -ne 0 ]]; then
          if grep -aq "no cipher list" $TMPFILE; then
               ret=5       # <--- important indicator for SSL2 (maybe others, too)
          else
               # try again without $PROXY
               $OPENSSL s_client $(s_client_options "-state $protos $STARTTLS $BUGS -connect $NODEIP:$PORT $SNI") >$TMPFILE 2>&1 </dev/null
               sclient_connect_successful $? $TMPFILE
               ret=$?
               debugme grep -E "error|failure" $ERRFILE | grep -Eav "unable to get local|verify error"
               grep -aq "no cipher list" $TMPFILE && ret=5       # <--- important indicator for SSL2 (maybe others, too)
          fi
     fi
     if [[ $ret -eq 0 ]]; then
          proto="$(get_protocol "$TMPFILE")"
          proto=${proto/\./_}
          proto=${proto/v/}
          proto="-$(tolower $proto)"
          [[ "$proto" != $1 ]] && ret=2
          case "$proto" in
               -ssl3) DETECTED_TLS_VERSION="0300" ;;
               -tls1) DETECTED_TLS_VERSION="0301" ;;
               -tls1_1) DETECTED_TLS_VERSION="0302" ;;
               -tls1_2) DETECTED_TLS_VERSION="0303" ;;
               -tls1_3) DETECTED_TLS_VERSION="0304" ;;
          esac
     fi
     tmpfile_handle ${FUNCNAME[0]}$1.txt
     return $ret

     # 0: offered
     # 1: not offered
     # 2: downgraded
     # 5: protocol ok, but no cipher
     # 7: no local support
}

# Idempotent function to add SSL/TLS protocols. It should accelerate testing.
# PROTOS_OFFERED can be e.g. "ssl2:no ssl3:no tls1_2:yes" which means that
# SSLv2 and SSLv3 was tested but not available, TLS 1.2 was tested and available
# TLS 1.0 and TLS 1.2 not tested yet
#
# arg1: protocol
# arg2: available (yes) or not (no)
add_proto_offered() {
     # the ":" is mandatory here (and @ other places), otherwise e.g. tls1 will match tls1_2
     if [[ "$PROTOS_OFFERED" =~ $1: ]]; then
          # we got that protcol already
          :
     else
          PROTOS_OFFERED+="${1}:$2 "
     fi
}

# function which checks whether SSLv2 - TLS 1.2 is being offered, see add_proto_offered()
# arg1:    protocol string or hex code for TLS protocol
# echos:   0 if proto known being offered, 1: known not being offered, 2: we don't know yet whether proto is being offered
# return value is always zero
has_server_protocol() {
     local proto
     local proto_val_pair

     case "$1" in
          04) proto="tls1_3" ;;
          03) proto="tls1_2" ;;
          02) proto="tls1_1" ;;
          01) proto="tls1" ;;
          00) proto="ssl3" ;;
           *) proto="$1" ;;
     esac

     if [[ "$PROTOS_OFFERED" =~ $proto: ]]; then
          for proto_val_pair in $PROTOS_OFFERED; do
               if [[ $proto_val_pair =~ $proto: ]]; then
                    if [[ ${proto_val_pair#*:} == yes ]]; then
                         echo 0
                         return 0
                    else
                         echo 1
                         return 0
                    fi
               fi
          done
     else
          # if empty echo 2, hinting to the caller to check at additional cost/connect
          echo 2
          return 0
     fi
}


# the protocol check needs to be revamped. It sucks, see above
run_protocols() {
     local using_sockets=true
     local supported_no_ciph1="supported but couldn't detect a cipher (may need debugging)"
     local supported_no_ciph2="supported but couldn't detect a cipher"
     local latest_supported=""  # version.major and version.minor of highest version supported by the server
     local detected_version_string latest_supported_string
     local key_share_extn_nr="$KEY_SHARE_EXTN_NR"
     local lines nr_ciphers_detected
     local tls13_ciphers_to_test=""
     local i drafts_offered=""  drafts_offered_str="" supported_versions debug_recomm=""
     local tls12_detected_version
     local -i ret=0 ret_val_ssl3 ret_val_tls1 ret_val_tls11 ret_val_tls12=0 ret_val_tls13=0
     local offers_tls13=false
     local jsonID="SSLv2"

     outln; pr_headline " Testing protocols "

     if "$SSL_NATIVE"; then
          using_sockets=false
          prln_underline "via native openssl"
     else
          using_sockets=true
          if [[ -n "$STARTTLS" ]]; then
               prln_underline "via sockets "
          else
               prln_underline "via sockets except NPN+ALPN "
          fi
     fi
     outln
     [[ "$DEBUG" -le 1 ]] && debug_recomm=", rerun with DEBUG>=2 or --ssl-native"

     pr_bold " SSLv2      ";
     if ! "$SSL_NATIVE"; then
          sslv2_sockets
          case $? in
               6) # couldn't open socket
                    prln_fixme "couldn't open socket"
                    fileout "$jsonID" "WARN" "couldn't be tested, socket problem"
                    ((ret++))
                    ;;
               7) # strange reply, couldn't convert the cipher spec length to a hex number
                    pr_cyan "strange v2 reply "
                    outln "$debug_recomm"
                    [[ $DEBUG -ge 3 ]] && hexdump -C "$TEMPDIR/$NODEIP.sslv2_sockets.dd" | head -1
                    fileout "$jsonID" "WARN" "received a strange SSLv2 reply (rerun with DEBUG>=2)"
                    ;;
               1) # no sslv2 server hello returned, like in openlitespeed which returns HTTP!
                    prln_svrty_best "not offered (OK)"
                    fileout "$jsonID" "OK" "not offered"
                    add_proto_offered ssl2 no
                    ;;
               0) # reset
                    prln_svrty_best "not offered (OK)"
                    fileout "$jsonID" "OK" "not offered"
                    add_proto_offered ssl2 no
                    ;;
               4)   out "likely "; pr_svrty_best "not offered (OK), "
                    fileout "$jsonID" "OK" "likely not offered"
                    add_proto_offered ssl2 no
                    pr_warning "received 4xx/5xx after STARTTLS handshake"; outln "$debug_recomm"
                    fileout "$jsonID" "WARN" "received 4xx/5xx after STARTTLS handshake${debug_recomm}"
                    ;;
               3)   lines=$(count_lines "$(hexdump -C "$TEMPDIR/$NODEIP.sslv2_sockets.dd" 2>/dev/null)")
                    [[ "$DEBUG" -ge 2 ]] && tm_out "  ($lines lines)  "
                    if [[ "$lines" -gt 1 ]]; then
                         nr_ciphers_detected=$((V2_HELLO_CIPHERSPEC_LENGTH / 3))
                         add_proto_offered ssl2 yes
                         set_grade_cap "F" "SSLv2 is offered"
                         if [[ 0 -eq "$nr_ciphers_detected" ]]; then
                              prln_svrty_high "supported but couldn't detect a cipher and vulnerable to CVE-2015-3197 ";
                              fileout "$jsonID" "HIGH" "offered, no cipher" "CVE-2015-3197" "CWE-310"
                         else
                              pr_svrty_critical "offered (NOT ok), also VULNERABLE to DROWN attack";
                              outln " -- $nr_ciphers_detected ciphers"
                              fileout "$jsonID" "CRITICAL" "vulnerable with $nr_ciphers_detected ciphers"
                         fi
                    fi
                    ;;
               *)   pr_fixme "unexpected value around line $((LINENO))"; outln "$debug_recomm"
                    ((ret++))
                    ;;
          esac
          debugme tmln_out
     else
          run_prototest_openssl "-ssl2"
          case $? in
               0)   prln_svrty_critical   "offered (NOT ok)"
                    fileout "$jsonID" "CRITICAL" "offered"
                    add_proto_offered ssl2 yes
                    set_grade_cap "F" "SSLv2 is offered"
                    ;;
               1)   prln_svrty_best "not offered (OK)"
                    fileout "$jsonID" "OK" "not offered"
                    add_proto_offered ssl2 no
                    ;;
               5)   prln_svrty_high "CVE-2015-3197: $supported_no_ciph2";
                    fileout "$jsonID" "HIGH" "offered, no cipher" "CVE-2015-3197" "CWE-310"
                    add_proto_offered ssl2 yes
                    set_grade_cap "F" "SSLv2 is offered"
                    ;;
               7)   prln_local_problem "$OPENSSL doesn't support \"s_client -ssl2\""
                    fileout "$jsonID" "INFO" "not tested due to lack of local support"
                    ((ret++))
                    ;;
          esac
     fi

     pr_bold " SSLv3      ";
     jsonID="SSLv3"
     if [[ $(has_server_protocol ssl3) -eq 0 ]]; then
          ret_val_ssl3=0
     elif "$using_sockets"; then
          tls_sockets "00" "$TLS_CIPHER"
          ret_val_ssl3=$?
     else
          run_prototest_openssl "-ssl3"
          ret_val_ssl3=$?
     fi
     case $ret_val_ssl3 in
          0)   prln_svrty_high "offered (NOT ok)"
               fileout "$jsonID" "HIGH" "offered"
               if "$using_sockets" || "$HAS_SSL3"; then
                    latest_supported="0300"
                    latest_supported_string="SSLv3"
               fi
               add_proto_offered ssl3 yes
               set_grade_cap "B" "SSLv3 is offered"
               ;;
          1)   prln_svrty_best "not offered (OK)"
               fileout "$jsonID" "OK" "not offered"
               add_proto_offered ssl3 no
               ;;
          2)   if [[ "$DETECTED_TLS_VERSION" == 03* ]]; then
                    detected_version_string="TLSv1.$((0x$DETECTED_TLS_VERSION-0x0301))"
                    prln_svrty_critical "server responded with higher version number ($detected_version_string) than requested by client (NOT ok)"
                    fileout "$jsonID" "CRITICAL" "server responded with higher version number ($detected_version_string) than requested by client"
               else
                    if [[ ${#DETECTED_TLS_VERSION} -eq 4 ]]; then
                         prln_svrty_critical "server responded with version number ${DETECTED_TLS_VERSION:0:2}.${DETECTED_TLS_VERSION:2:2} (NOT ok)"
                         fileout "$jsonID" "CRITICAL" "server responded with version number ${DETECTED_TLS_VERSION:0:2}.${DETECTED_TLS_VERSION:2:2}"
                    else
                         prln_svrty_medium "strange, server ${DETECTED_TLS_VERSION}"
                         fileout "$jsonID" "MEDIUM" "strange, server ${DETECTED_TLS_VERSION}"
                         ((ret++))
                    fi
               fi
               ;;
          3)   pr_svrty_best "not offered (OK), "
               fileout "$jsonID" "OK" "not offered"
               add_proto_offered ssl3 no
               pr_warning "SSL downgraded to STARTTLS plaintext"; outln
               fileout "$jsonID" "WARN" "SSL downgraded to STARTTLS plaintext"
               ;;
          4)   out "likely "; pr_svrty_best "not offered (OK), "
               fileout "$jsonID" "OK" "not offered"
               add_proto_offered ssl3 no
               pr_warning "received 4xx/5xx after STARTTLS handshake"; outln "$debug_recomm"
               fileout "$jsonID" "WARN" "received 4xx/5xx after STARTTLS handshake${debug_recomm}"
               ;;
          5)   pr_svrty_high "$supported_no_ciph1"               # protocol detected but no cipher --> comes from run_prototest_openssl
               fileout "$jsonID" "HIGH" "$supported_no_ciph1"
               add_proto_offered ssl3 yes
               set_grade_cap "B" "SSLv3 is offered"
               ;;
          7)   if "$using_sockets" ; then
                    # can only happen in debug mode
                    pr_warning "strange reply, maybe a client side problem with SSLv3"; outln "$debug_recomm"
               else
                    prln_local_problem "$OPENSSL doesn't support \"s_client -ssl3\""
                    fileout "$jsonID" "WARN" "not tested due to lack of local support"
               fi
               ;;
          *)   pr_fixme "unexpected value around line $((LINENO))"; outln "$debug_recomm"
               ((ret++))
               ;;
     esac

     pr_bold " TLS 1      ";
     jsonID="TLS1"
     if [[ $(has_server_protocol tls1) -eq 0 ]]; then
          ret_val_tls1=0
     elif "$using_sockets"; then
          tls_sockets "01" "$TLS_CIPHER"
          ret_val_tls1=$?
     else
          run_prototest_openssl "-tls1"
          ret_val_tls1=$?
     fi
     case $ret_val_tls1 in
          0)   pr_svrty_low "offered" ; outln " (deprecated)"
               fileout "$jsonID" "LOW" "offered (deprecated)"
               latest_supported="0301"
               latest_supported_string="TLSv1.0"
               add_proto_offered tls1 yes
               set_grade_cap "B" "TLS 1.0 offered"
               ;;                                                # nothing wrong with it -- per se
          1)   out "not offered"
               add_proto_offered tls1 no
               if [[ -z $latest_supported ]]; then
                    outln
                    fileout "$jsonID" "INFO" "not offered"       # neither good or bad
               else
                    prln_svrty_critical " -- connection failed rather than downgrading to $latest_supported_string (NOT ok)"
                    fileout "$jsonID" "CRITICAL" "connection failed rather than downgrading to $latest_supported_string"
               fi
               ;;
          2)   pr_svrty_medium "not offered"
               add_proto_offered tls1 no
               if [[ "$DETECTED_TLS_VERSION" == 0300 ]]; then
                    [[ $DEBUG -ge 1 ]] && tm_out " -- downgraded"
                    outln
                    fileout "$jsonID" "MEDIUM" "not offered, and downgraded to SSL"
               elif [[ "$DETECTED_TLS_VERSION" == 03* ]]; then
                    detected_version_string="TLSv1.$((0x$DETECTED_TLS_VERSION-0x0301))"
                    prln_svrty_critical " -- server responded with higher version number ($detected_version_string) than requested by client"
                    fileout "$jsonID" "CRITICAL" "server responded with higher version number ($detected_version_string) than requested by client"
               else
                    if [[ ${#DETECTED_TLS_VERSION} -eq 4 ]]; then
                         prln_svrty_critical "server responded with version number ${DETECTED_TLS_VERSION:0:2}.${DETECTED_TLS_VERSION:2:2} (NOT ok)"
                         fileout "$jsonID" "CRITICAL" "server responded with version number ${DETECTED_TLS_VERSION:0:2}.${DETECTED_TLS_VERSION:2:2}"
                    else
                         prln_svrty_medium " -- strange, server ${DETECTED_TLS_VERSION}"
                         fileout "$jsonID" "MEDIUM" "strange, server ${DETECTED_TLS_VERSION}"
                    fi
               fi
               ;;
          3)   out "not offered, "
               fileout "$jsonID" "OK" "not offered"
               add_proto_offered tls1 no
               pr_warning "TLS downgraded to STARTTLS plaintext"; outln
               fileout "$jsonID" "WARN" "TLS downgraded to STARTTLS plaintext"
               ;;
          4)   out "likely not offered, "
               fileout "$jsonID" "INFO" "likely not offered"
               add_proto_offered tls1 no
               pr_warning "received 4xx/5xx after STARTTLS handshake"; outln "$debug_recomm"
               fileout "$jsonID" "WARN" "received 4xx/5xx after STARTTLS handshake${debug_recomm}"
               ;;
          5)   outln "$supported_no_ciph1"                                 # protocol detected but no cipher --> comes from run_prototest_openssl
               fileout "$jsonID" "INFO" "$supported_no_ciph1"
               add_proto_offered tls1 yes
               set_grade_cap "B" "TLS 1.0 offered"
               ;;
          7)   if "$using_sockets" ; then
                    # can only happen in debug mode
                    pr_warning "strange reply, maybe a client side problem with TLS 1.0"; outln "$debug_recomm"
               else
                    prln_local_problem "$OPENSSL doesn't support \"s_client -tls1\""
                    fileout "$jsonID" "WARN" "not tested due to lack of local support"
               fi
               ((ret++))
               ;;
          *)   pr_fixme "unexpected value around line $((LINENO))"; outln "$debug_recomm"
               ((ret++))
               ;;
     esac

     pr_bold " TLS 1.1    ";
     jsonID="TLS1_1"
     if [[ $(has_server_protocol tls1_1) -eq 0 ]]; then
          ret_val_tls11=0
     elif "$using_sockets"; then
          tls_sockets "02" "$TLS_CIPHER"
          ret_val_tls11=$?
     else
          run_prototest_openssl "-tls1_1"
          ret_val_tls11=$?
     fi
     case $ret_val_tls11 in
          0)   pr_svrty_low "offered" ; outln " (deprecated)"
               fileout "$jsonID" "LOW" "offered (deprecated)"
               latest_supported="0302"
               latest_supported_string="TLSv1.1"
               add_proto_offered tls1_1 yes
               set_grade_cap "B" "TLS 1.1 offered"
               ;;                                                # nothing wrong with it
          1)   out "not offered"
               add_proto_offered tls1_1 no
               if [[ -z $latest_supported ]]; then
                    outln
                    fileout "$jsonID" "INFO" "is not offered"    # neither good or bad
               else
                    prln_svrty_critical " -- connection failed rather than downgrading to $latest_supported_string"
                    fileout "$jsonID" "CRITICAL" "connection failed rather than downgrading to $latest_supported_string"
               fi
               ;;
          2)   out "not offered"
               add_proto_offered tls1_1 no
               if [[ "$DETECTED_TLS_VERSION" == "$latest_supported" ]]; then
                    [[ $DEBUG -ge 1 ]] && tm_out " -- downgraded"
                    outln
                    fileout "$jsonID" "CRITICAL" "TLSv1.1 is not offered, and downgraded to a weaker protocol"
               elif [[ "$DETECTED_TLS_VERSION" == 0300 ]] && [[ "$latest_supported" == 0301 ]]; then
                    prln_svrty_critical " -- server supports TLSv1.0, but downgraded to SSLv3 (NOT ok)"
                    fileout "$jsonID" "CRITICAL" "not offered, and downgraded to SSLv3 rather than TLSv1.0"
               elif [[ "$DETECTED_TLS_VERSION" == 03* ]] && [[ 0x$DETECTED_TLS_VERSION -gt 0x0302 ]]; then
                    detected_version_string="TLSv1.$((0x$DETECTED_TLS_VERSION-0x0301))"
                    prln_svrty_critical " -- server responded with higher version number ($detected_version_string) than requested by client (NOT ok)"
                    fileout "$jsonID" "CRITICAL" "not offered, server responded with higher version number ($detected_version_string) than requested by client"
               else
                    if [[ ${#DETECTED_TLS_VERSION} -eq 4 ]]; then
                         prln_svrty_critical "server responded with version number ${DETECTED_TLS_VERSION:0:2}.${DETECTED_TLS_VERSION:2:2} (NOT ok)"
                         fileout "$jsonID" "CRITICAL" "server responded with version number ${DETECTED_TLS_VERSION:0:2}.${DETECTED_TLS_VERSION:2:2}"
                    else
                         prln_svrty_medium " -- strange, server ${DETECTED_TLS_VERSION}"
                         fileout "$jsonID" "MEDIUM" "strange, server ${DETECTED_TLS_VERSION}"
                    fi
               fi
               ;;
          3)   out "not offered, "
               fileout "$jsonID" "OK" "not offered"
               add_proto_offered tls1_1 no
               pr_warning "TLS downgraded to STARTTLS plaintext"; outln
               fileout "$jsonID" "WARN" "TLS downgraded to STARTTLS plaintext"
               ;;
          4)   out "likely not offered, "
               fileout "$jsonID" "INFO" "is not offered"
               add_proto_offered tls1_1 no
               pr_warning "received 4xx/5xx after STARTTLS handshake"; outln "$debug_recomm"
               fileout "$jsonID" "WARN" "received 4xx/5xx after STARTTLS handshake${debug_recomm}"
               ;;
          5)   outln "$supported_no_ciph1"                       # protocol detected but no cipher --> comes from run_prototest_openssl
               fileout "$jsonID" "INFO" "$supported_no_ciph1"
               add_proto_offered tls1_1 yes
               set_grade_cap "B" "TLS 1.1 offered"
               ;;
          7)   if "$using_sockets" ; then
                    # can only happen in debug mode
                    pr_warning "strange reply, maybe a client side problem with TLS 1.1"; outln "$debug_recomm"
               else
                    prln_local_problem "$OPENSSL doesn't support \"s_client -tls1_1\""
                    fileout "$jsonID" "WARN" "not tested due to lack of local support"
               fi
               ((ret++))
               ;;
          *)   pr_fixme "unexpected value around line $((LINENO))"; outln "$debug_recomm"
               ((ret++))
               ;;
     esac

     # Now, we are doing a basic/pre test for TLS 1.2 and 1.3 in order not to penalize servers (medium)
     # running TLS 1.3 only when TLS 1.2 is not offered.  0 and 5 are the return codes for
     # TLS 1.3 support (kind of, including deprecated pre-versions of TLS 1.3)
     if [[ $(has_server_protocol tls1_2) -eq 0 ]]; then
          ret_val_tls12=0
     elif "$using_sockets"; then
          tls_sockets "03" "$TLS12_CIPHER"
          ret_val_tls12=$?
          tls12_detected_version="$DETECTED_TLS_VERSION"
     else
          run_prototest_openssl "-tls1_2"
          ret_val_tls12=$?
          tls12_detected_version="$DETECTED_TLS_VERSION"
     fi

     if [[ $(has_server_protocol tls1_3) -eq 0 ]]; then
          ret_val_tls13=0
     elif "$using_sockets"; then
          # Need to ensure that at most 128 ciphers are included in ClientHello.
          # If the TLSv1.2 test in determine_optimal_sockets_params()  was successful,
          # then use the 5 TLSv1.3 ciphers plus the cipher selected in the TLSv1.2 test.
          # If the TLSv1.2 test was not successful, then just use the 5 TLSv1.3 ciphers
          # plus the list of ciphers used in all of the previous tests ($TLS_CIPHER).
          if [[ -n "$TLS12_CIPHER_OFFERED" ]]; then
               tls13_ciphers_to_test="$TLS13_CIPHER, $TLS12_CIPHER_OFFERED, 00,ff"
          else
               tls13_ciphers_to_test="$TLS13_CIPHER,$TLS_CIPHER"
          fi
          tls_sockets "04" "$tls13_ciphers_to_test"
          ret_val_tls13=$?
     else
          run_prototest_openssl "-tls1_3"
          ret_val_tls13=$?
     fi
     if [[ $ret_val_tls13 -eq 0 ]] || [[ $ret_val_tls13 -eq 5 ]]; then
          offers_tls13=true             # This variable comes in handy for further if statements below
     fi
     # Done with pretesting TLS 1.2 and 1.3.

     pr_bold " TLS 1.2    ";
     jsonID="TLS1_2"
     case $ret_val_tls12 in
          0)   prln_svrty_best "offered (OK)"
               fileout "$jsonID" "OK" "offered"
               latest_supported="0303"
               latest_supported_string="TLSv1.2"
               add_proto_offered tls1_2 yes
               ;;                                                     # GCM cipher in TLS 1.2: very good!
          1)   add_proto_offered tls1_2 no
               if "$offers_tls13"; then
                    out "not offered"
               else
                    pr_svrty_medium "not offered"
               fi
               if [[ -z $latest_supported ]]; then
                    outln
                    if "$offers_tls13"; then
                         fileout "$jsonID" "INFO" "not offered"
                    else
                         fileout "$jsonID" "MEDIUM" "not offered"     # TLS 1.3, no TLS 1.2 --> no GCM, penalty
                         set_grade_cap "C" "TLS 1.2 or TLS 1.3 are not offered"
                    fi
               else
                    prln_svrty_critical " -- connection failed rather than downgrading to $latest_supported_string"
                    fileout "$jsonID" "CRITICAL" "connection failed rather than downgrading to $latest_supported_string"
               fi
               ;;
          2)   add_proto_offered tls1_2 no
               set_grade_cap "C" "TLS 1.2 is not offered"
               pr_svrty_medium "not offered and downgraded to a weaker protocol"
               if [[ "$tls12_detected_version" == 0300 ]]; then
                    detected_version_string="SSLv3"
               elif [[ "$tls12_detected_version" == 03* ]]; then
                    detected_version_string="TLSv1.$((0x$tls12_detected_version-0x0301))"
               fi
               if [[ "$tls12_detected_version" == "$latest_supported" ]]; then
                    outln
                    fileout "$jsonID" "MEDIUM" "not offered and downgraded to a weaker protocol"
               elif [[ "$tls12_detected_version" == 03* ]] && [[ 0x$tls12_detected_version -lt 0x$latest_supported ]]; then
                    prln_svrty_critical " -- server supports $latest_supported_string, but downgraded to $detected_version_string"
                    fileout "$jsonID" "CRITICAL" "not offered, and downgraded to $detected_version_string rather than $latest_supported_string"
               elif [[ "$tls12_detected_version" == 03* ]] && [[ 0x$tls12_detected_version -gt 0x0303 ]]; then
                    prln_svrty_critical " -- server responded with higher version number ($detected_version_string) than requested by client"
                    fileout "$jsonID" "CRITICAL" "not offered, server responded with higher version number ($detected_version_string) than requested by client"
               else
                    if [[ ${#tls12_detected_version} -eq 4 ]]; then
                         prln_svrty_critical "server responded with version number ${tls12_detected_version:0:2}.${tls12_detected_version:2:2} (NOT ok)"
                         fileout "$jsonID" "CRITICAL" "server responded with version number ${tls12_detected_version:0:2}.${tls12_detected_version:2:2}"
                    else
                         prln_svrty_medium " -- strange, server ${tls12_detected_version}"
                         fileout "$jsonID" "MEDIUM" "strange, server ${tls12_detected_version}"
                    fi
               fi
               ;;
          3)   out "not offered, "
               fileout "$jsonID" "INFO" "not offered"
               add_proto_offered tls1_2 no
               set_grade_cap "C" "TLS 1.2 is not offered"
               pr_warning "TLS downgraded to STARTTLS plaintext"; outln
               fileout "$jsonID" "WARN" "TLS downgraded to STARTTLS plaintext"
               ;;
          4)   out "likely "; pr_svrty_medium "not offered, "
               fileout "$jsonID" "MEDIUM" "not offered"
               add_proto_offered tls1_2 no
               set_grade_cap "C" "TLS 1.2 is not offered"
               pr_warning "received 4xx/5xx after STARTTLS handshake"; outln "$debug_recomm"
               fileout "$jsonID" "WARN" "received 4xx/5xx after STARTTLS handshake${debug_recomm}"
               ;;
          5)   outln "$supported_no_ciph1"                  # protocol detected, but no cipher --> comes from run_prototest_openssl
               fileout "$jsonID" "INFO" "$supported_no_ciph1"
               add_proto_offered tls1_2 yes
               ;;
          7)   if "$using_sockets" ; then
                    # can only happen in debug mode
                    pr_warning "strange reply, maybe a client side problem with TLS 1.2"; outln "$debug_recomm"
               else
                    prln_local_problem "$OPENSSL doesn't support \"s_client -tls1_2\""
                    fileout "$jsonID" "WARN" "not tested due to lack of local support"
               fi
               ((ret++))
               ;;
          *)   pr_fixme "unexpected value around line $((LINENO))"; outln "$debug_recomm"
               ((ret++))
               ;;
     esac

     pr_bold " TLS 1.3    ";
     jsonID="TLS1_3"
     case $ret_val_tls13 in
          0)   if ! "$using_sockets"; then
                    prln_svrty_best "offered (OK)"
                    fileout "$jsonID" "OK" "offered"
               else
                    # If TLS 1.3 is offered, then its support was detected
                    # by determine_optimal_sockets_params().
                    if [[ $(has_server_protocol tls1_3_rfc8446) -eq 0 ]]; then
                         drafts_offered+=" 0304 "
                    else
                         for i in 1C 1B 1A 19 18 17 16 15 14 13 12; do
                              if [[ $(has_server_protocol tls1_3_draft$(hex2dec "$i")) -eq 0 ]]; then
                                   drafts_offered+=" 7F$i "
                                   break
                              fi
                         done
                    fi
                    KEY_SHARE_EXTN_NR="28"
                    while true; do
                         supported_versions=""
                         for i in 16 15 14 13 12; do
                              [[ "$drafts_offered" =~ \ 7F$i\  ]] || supported_versions+=",7f,$i"
                         done
                         [[ -z "$supported_versions" ]] && break
                         supported_versions="00, 2b, 00, $(printf "%02x" $((${#supported_versions}/3+1))), $(printf "%02x" $((${#supported_versions}/3))) $supported_versions"
                         tls_sockets "04" "$TLS13_CIPHER" "" "$supported_versions"
                         [[ $? -eq 0 ]] || break
                         if [[ "${TLS_SERVER_HELLO:8:3}" == 7F1 ]]; then
                              drafts_offered+=" ${TLS_SERVER_HELLO:8:4} "
                         elif [[ "$TLS_SERVER_HELLO" =~ 002B00027F1[2-6] ]]; then
                              drafts_offered+=" ${BASH_REMATCH:8:4} "
                         fi
                    done
                    KEY_SHARE_EXTN_NR="33"
                    while true; do
                         supported_versions=""
                         for i in 1C 1B 1A 19 18 17; do
                              [[ "$drafts_offered" =~ \ 7F$i\  ]] || supported_versions+=",7f,$i"
                         done
                         [[ "$drafts_offered" =~ \ 0304\  ]] || supported_versions+=",03,04"
                         [[ -z "$supported_versions" ]] && break
                         supported_versions="00, 2b, 00, $(printf "%02x" $((${#supported_versions}/3+1))), $(printf "%02x" $((${#supported_versions}/3))) $supported_versions"
                         tls_sockets "04" "$TLS13_CIPHER" "" "$supported_versions"
                         [[ $? -eq 0 ]] || break
                         if [[ "$TLS_SERVER_HELLO" =~ 002B00020304 ]]; then
                              drafts_offered+=" 0304 "
                         elif [[ "$TLS_SERVER_HELLO" =~ 002B00027F1[7-9A-C] ]]; then
                              drafts_offered+=" ${BASH_REMATCH:8:4} "
                         fi
                    done
                    KEY_SHARE_EXTN_NR="$key_share_extn_nr"
                    if [[ -n "$drafts_offered" ]]; then
                         for i in 1C 1B 1A 19 18 17 16 15 14 13 12; do
                              if [[ "$drafts_offered" =~ \ 7F$i\  ]]; then
                                   [[ -n "$drafts_offered_str" ]] && drafts_offered_str+=", "
                                   drafts_offered_str+="draft $(printf "%d" 0x$i)"
                              fi
                         done
                         if [[ "$drafts_offered" =~ \ 0304\  ]]; then
                              [[ -n "$drafts_offered_str" ]] && drafts_offered_str+=", "
                              drafts_offered_str+="final"
                         fi
                         if [[ "$drafts_offered" =~ \ 0304\  ]]; then
                              pr_svrty_best "offered (OK)"; outln ": $drafts_offered_str"
                              fileout "$jsonID" "OK" "offered with $drafts_offered_str"
                         else
                              out "offered (OK)"; outln ": $drafts_offered_str"
                              fileout "$jsonID" "INFO" "offered with $drafts_offered_str"
                         fi
                    else
                         pr_warning "Unexpected results"; outln "$debug_recomm"
                         fileout "$jsonID" "WARN" "unexpected results"
                    fi
               fi
               latest_supported="0304"
               latest_supported_string="TLSv1.3"
               add_proto_offered tls1_3 yes
               ;;
          1)   pr_svrty_low "not offered"
               if [[ -z $latest_supported ]]; then
                    outln
                    fileout "$jsonID" "LOW" "not offered"
               else
                    prln_svrty_critical " -- connection failed rather than downgrading to $latest_supported_string"
                    fileout "$jsonID" "CRITICAL" "connection failed rather than downgrading to $latest_supported_string"
               fi
               add_proto_offered tls1_3 no
               ;;
          2)   if [[ "$DETECTED_TLS_VERSION" == 0300 ]]; then
                    detected_version_string="SSLv3"
               elif [[ "$DETECTED_TLS_VERSION" == 03* ]]; then
                    detected_version_string="TLSv1.$((0x$DETECTED_TLS_VERSION-0x0301))"
               fi
               if [[ "$DETECTED_TLS_VERSION" == "$latest_supported" ]]; then
                    outln "not offered and downgraded to a weaker protocol"
                    fileout "$jsonID" "INFO" "not offered + downgraded to weaker protocol"
               elif [[ "$DETECTED_TLS_VERSION" == 03* ]] && [[ 0x$DETECTED_TLS_VERSION -lt 0x$latest_supported ]]; then
                    out "not offered"
                    prln_svrty_critical " -- server supports $latest_supported_string, but downgraded to $detected_version_string"
                    fileout "$jsonID" "CRITICAL" "not offered, and downgraded to $detected_version_string rather than $latest_supported_string"
               elif [[ "$DETECTED_TLS_VERSION" == 03* ]] && [[ 0x$DETECTED_TLS_VERSION -gt 0x0304 ]]; then
                    out "not offered"
                    prln_svrty_critical " -- server responded with higher version number ($detected_version_string) than requested by client"
                    fileout "$jsonID" "CRITICAL" "not offered, server responded with higher version number ($detected_version_string) than requested by client"
               else
                    out "not offered"
                    prln_svrty_critical " -- server responded with version number ${DETECTED_TLS_VERSION:0:2}.${DETECTED_TLS_VERSION:2:2}"
                    fileout "$jsonID" "CRITICAL" "server responded with version number ${DETECTED_TLS_VERSION:0:2}.${DETECTED_TLS_VERSION:2:2}"
               fi
               add_proto_offered tls1_3 no
               ;;
          3)   out "not offered  "
               fileout "$jsonID" "INFO" "not offered"
               add_proto_offered tls1_3 no
               pr_warning "TLS downgraded to STARTTLS plaintext"; outln
               fileout "$jsonID" "WARN" "TLS downgraded to STARTTLS plaintext"
               ;;
          4)   out "likely not offered, "
               fileout "$jsonID" "INFO" "not offered"
               add_proto_offered tls1_3 no
               pr_warning "received 4xx/5xx after STARTTLS handshake"; outln "$debug_recomm"
               fileout "$jsonID" "WARN" "received 4xx/5xx after STARTTLS handshake${debug_recomm}"
               ;;
          5)   outln "$supported_no_ciph1"             # protocol detected but no cipher --> comes from run_prototest_openssl
               fileout "$jsonID" "INFO" "$supported_no_ciph1"
               add_proto_offered tls1_3 yes
               ;;
          7)   if "$using_sockets" ; then
                    # can only happen in debug mode
                    prln_warning "strange reply, maybe a client side problem with TLS 1.3"; outln "$debug_recomm"
               else
                    prln_local_problem "$OPENSSL doesn't support \"s_client -tls1_3\""
                    fileout "$jsonID" "WARN" "not tested due to lack of local support"
               fi
               ((ret++))
               ;;
          *)   pr_fixme "unexpected value around line $((LINENO))"; outln "$debug_recomm"
               ((ret++))
               ;;
     esac

     debugme echo "PROTOS_OFFERED: $PROTOS_OFFERED"
     if [[ ! "$PROTOS_OFFERED" =~ yes ]]; then
          outln
          ignore_no_or_lame "You should not proceed as no protocol was detected. If you still really really want to, say \"YES\"" "YES"
          [[ $? -ne 0 ]] && exit $ERR_CLUELESS
     fi

     return $ret
}


# list ciphers (and makes sure you have them locally configured)
# arg[1]: non-TLSv1.3 cipher list (or anything else)
# arg[2]: TLSv1.3 cipher list
# arg[3]: protocol (e.g., -ssl2)
#
listciphers() {
     local -i ret
     local debugname=""
     local ciphers="$1"
     local tls13_ciphers="$TLS13_OSSL_CIPHERS"

     [[ "$2" != ALL ]] && tls13_ciphers="$2"
     "$HAS_SECLEVEL" && [[ -n "$ciphers" ]] && ciphers="@SECLEVEL=0:$1"
     if "$HAS_CIPHERSUITES"; then
          $OPENSSL ciphers $OSSL_CIPHERS_S $3 -ciphersuites "$tls13_ciphers" "$ciphers" &>$TMPFILE
     elif [[ -n "$tls13_ciphers" ]]; then
          $OPENSSL ciphers $OSSL_CIPHERS_S $3 "$tls13_ciphers:$ciphers" &>$TMPFILE
     else
          $OPENSSL ciphers $OSSL_CIPHERS_S $3 "$ciphers" &>$TMPFILE
     fi
     ret=$?
     debugme cat $TMPFILE
     debugname="$(sed -e s'/\!/not/g' -e 's/\:/_/g' <<< "$1")"
     tmpfile_handle ${FUNCNAME[0]}.${debugname}.txt
     return $ret
}


# argv[1]: non-TLSv1.3 cipher list to test in OpenSSL syntax
# argv[2]: TLSv1.3 cipher list to test in OpenSSL syntax
# argv[3]: string on console / HTML or "finding"
# argv[4]: rating whether ok to offer
# argv[5]: string to be appended for fileout
# argv[6]: non-SSLv2 cipher list to test (hexcodes), if using sockets
# argv[7]: SSLv2 cipher list to test (hexcodes), if using sockets
# argv[8]: true if using sockets, false if not
# argv[9]: CVE
# argv[10]: CWE
#
sub_cipherlists() {
     local -i i len sclient_success=1
     local cipherlist sslv2_cipherlist detected_ssl2_ciphers
     local singlespaces
     local proto=""
     local -i ret=0
     local jsonID="cipherlist"
     local using_sockets="${8}"
     local cve="${9}"
     local cwe="${10}"

     pr_bold "$3  "
     [[ "$OPTIMAL_PROTO" == -ssl2 ]] && proto="$OPTIMAL_PROTO"
     jsonID="${jsonID} quality: $5"

     if "$using_sockets" || listciphers "$1" "$2" $proto; then
          if ! "$using_sockets" || ( "$FAST" && listciphers "$1" "$2" -tls1 ); then
               for proto in -no_ssl2 -tls1_2 -tls1_1 -tls1 -ssl3; do
                    if [[ "$proto" == -tls1_2 ]]; then
                         # If $OPENSSL doesn't support TLSv1.3 or if no TLSv1.3
                         # ciphers are being tested, then a TLSv1.2 ClientHello
                         # was tested in the first iteration.
                         ! "$HAS_TLS13" && continue
                         [[ -z "$2" ]] && continue
                    fi
                    ! "$HAS_SSL3" && [[ "$proto" == -ssl3 ]] && continue
                    if [[ "$proto" != -no_ssl2 ]]; then
                         "$FAST" && continue
                         [[ $(has_server_protocol "${proto:1}") -eq 1 ]] && continue
                    fi
                    $OPENSSL s_client $(s_client_options "-cipher "$1" -ciphersuites "\'$2\'" $BUGS $STARTTLS -connect $NODEIP:$PORT $PROXY $SNI $proto") 2>$ERRFILE >$TMPFILE </dev/null
                    sclient_connect_successful $? $TMPFILE
                    sclient_success=$?
                    debugme cat $ERRFILE
                    [[ $sclient_success -eq 0 ]] && break
               done
          else
               for proto in 04 03 02 01 00; do
                    # If $cipherlist doesn't contain any TLSv1.3 ciphers, then there is
                    # no reason to try a TLSv1.3 ClientHello.
                    [[ "$proto" == 04 ]] && [[ ! "$6" =~ 13,0 ]] && continue
                    [[ $(has_server_protocol "$proto") -eq 1 ]] && continue
                    cipherlist="$(strip_inconsistent_ciphers "$proto" ", $6")"
                    cipherlist="${cipherlist:2}"
                    if [[ -n "$cipherlist" ]] && [[ "$cipherlist" != 00,ff ]]; then
                         tls_sockets "$proto" "$cipherlist"
                         sclient_success=$?
                         [[ $sclient_success -eq 2 ]] && sclient_success=0
                         [[ $sclient_success -eq 0 ]] && break
                    fi
               done
          fi
          if [[ $sclient_success -ne 0 ]] && [[ 1 -ne $(has_server_protocol ssl2) ]]; then
               if ( [[ -z "$7" ]] || "$FAST" ) && "$HAS_SSL2" && listciphers "$1" "" -ssl2; then
                    $OPENSSL s_client -cipher "$1" $BUGS $STARTTLS -connect $NODEIP:$PORT $PROXY -ssl2 2>$ERRFILE >$TMPFILE </dev/null
                    sclient_connect_successful $? $TMPFILE
                    sclient_success=$?
                    debugme cat $ERRFILE
               elif [[ -n "$7" ]]; then
                    sslv2_sockets "$7" "true"
                    if [[ $? -eq 3 ]] && [[ "$V2_HELLO_CIPHERSPEC_LENGTH" -ne 0 ]]; then
                         sslv2_cipherlist="$(strip_spaces "${6//,/}")"
                         len=${#sslv2_cipherlist}
                         detected_ssl2_ciphers="$(grep "Supported cipher: " "$TEMPDIR/$NODEIP.parse_sslv2_serverhello.txt")"
                         for (( i=0; i<len; i+=6 )); do
                              [[ "$detected_ssl2_ciphers" =~ "x${sslv2_cipherlist:i:6}" ]] && sclient_success=0 && break
                         done
                    fi
               fi
          fi
          if [[ $sclient_success -ne 0 ]] && $BAD_SERVER_HELLO_CIPHER; then
               # If server failed with a known error, raise it to the user.
               if [[ $STARTTLS_PROTOCOL == mysql ]]; then
                    pr_warning "SERVER_ERROR: test inconclusive due to MySQL Community Edition (yaSSL) bug."
                    fileout "$jsonID" "WARN" "SERVER_ERROR, test inconclusive due to MySQL Community Edition (yaSSL) bug." "$cve" "$cwe"
               else
                    pr_warning "SERVER_ERROR: test inconclusive."
                    fileout "$jsonID" "WARN" "SERVER_ERROR, test inconclusive." "$cve" "$cwe"
               fi
               ((ret++))
          else
               # Otherwise the error means the server doesn't support that cipher list.
               case $4 in
                    7)   if [[ $sclient_success -eq 0 ]]; then
                              # Strong is excellent to offer
                              pr_svrty_best "offered (OK)"
                              fileout "$jsonID" "OK" "offered" "$cve" "$cwe"
                         else
                              pr_svrty_medium "not offered"
                              fileout "$jsonID" "MEDIUM" "not offered" "$cve" "$cwe"
                         fi
                         ;;
                    6)   if [[ $sclient_success -eq 0 ]]; then
                              # High is good to offer
                              pr_svrty_good "offered (OK)"
                              fileout "$jsonID" "OK" "offered" "$cve" "$cwe"
                         else
                              # FIXME: we don't penalize the absence of high, but perhaps
                              # we should if there is also no strong encryption (next)
                              out "not offered"
                              fileout "$jsonID" "INFO" "not offered" "$cve" "$cwe"
                         fi
                         ;;
                    5)   if [[ $sclient_success -eq 0 ]]; then
                              # Neither good nor bad to offer
                              out "offered (OK)"
                              fileout "$jsonID" "INFO" "offered" "$cve" "$cwe"
                         else
                              out "not offered"
                              fileout "$jsonID" "INFO" "not offered" "$cve" "$cwe"
                         fi
                         ;;
                    4)   if [[ $sclient_success -eq 0 ]]; then
                              # medium is not that bad
                              pr_svrty_low "offered"
                              fileout "$jsonID" "LOW" "$3\n" "$cve" "$cwe"
                         else
                              out "not offered"
                              fileout "$jsonID" "INFO" "not offered" "$cve" "$cwe"
                         fi
                         ;;
                    3)   if [[ $sclient_success -eq 0 ]]; then
                              pr_svrty_medium "offered"
                              fileout "$jsonID" "MEDIUM" "$3\n" "$cve" "$cwe"
                         else
                              out "not offered"
                              fileout "$jsonID" "INFO" "not offered" "$cve" "$cwe"
                         fi
                         ;;
                    2)   if [[ $sclient_success -eq 0 ]]; then
                              # bad but there is worse
                              pr_svrty_high "offered (NOT ok)"
                              fileout "$jsonID" "HIGH" "$3\n" "$cve" "$cwe"
                         else
                              # need a check for -eq 1 here
                              pr_svrty_good "not offered (OK)"
                              fileout "$jsonID" "OK" "not offered" "$cve" "$cwe"
                         fi
                         ;;
                    1)   if [[ $sclient_success -eq 0 ]]; then
                              # the ugly ones
                              pr_svrty_critical "offered (NOT ok)"
                              fileout "$jsonID" "CRITICAL" "$3\n" "$cve" "$cwe"
                         else
                              pr_svrty_best "not offered (OK)"
                              fileout "$jsonID" "OK" "not offered" "$cve" "$cwe"
                         fi
                         ;;
                    *)   # we shouldn't reach this
                         pr_warning "?: $4 (please report this)"
                         fileout "$jsonID" "WARN" "return condition $4 unclear" "$cve" "$cwe"
                         ((ret++))
                         ;;
               esac

               # Not a perfect place here. A new one should be picked in the future
               [[ $sclient_success -eq 0 && "$1" =~ (^|:)EXPORT(:|$) ]] && set_grade_cap "F" "Export suite offered"
               [[ $sclient_success -eq 0 && "$1" =~ AEAD ]] && set_grade_cap "B" "No AEAD ciphers offered"
          fi
          tmpfile_handle ${FUNCNAME[0]}.${5}.txt
          [[ $DEBUG -ge 1 ]] && tm_out " -- $1"
          outln
     else
          singlespaces=$(sed -e 's/ \+/ /g' -e 's/^ //' -e 's/ $//g' -e 's/  //g' <<< "$3")
          if [[ "$OPTIMAL_PROTO" == -ssl2 ]]; then
               prln_local_problem "No $singlespaces for SSLv2 configured in $OPENSSL"
          else
               prln_local_problem "No $singlespaces configured in $OPENSSL"
          fi
          fileout "$jsonID" "WARN" "Cipher $3 ($1) not supported by local OpenSSL ($OPENSSL)"
     fi
     return $ret
}

#TODO: work with fixed lists here --> atm ok, as sockets are preferred. If there would be a single function for testing: yes.
run_cipherlists() {
     local hexc hexcode strength
     local -i i
     local -i ret=0
     local ossl_null_ciphers null_ciphers sslv2_null_ciphers
     local ossl_anon_ciphers anon_ciphers sslv2_anon_ciphers
     local ossl_exp_ciphers exp_ciphers sslv2_exp_ciphers
     local ossl_low_ciphers low_ciphers sslv2_low_ciphers
     local ossl_tdes_ciphers tdes_ciphers sslv2_tdes_cipher
     local ossl_obsoleted_ciphers obsoleted_ciphers
     local strong_ciphers
     local cwe="CWE-327"
     local cwe2="CWE-310"
     local cve=""
     local using_sockets=true

     outln
     pr_headlineln " Testing cipher categories "
     outln
     "$SSL_NATIVE" && using_sockets=false

     # conversion 2 byte ciphers via:  echo "$@" | sed -e 's/[[:xdigit:]]\{2\},/0x&/g'  -e 's/, /\n/g' | while read ci; do grep -wi $ci etc/cipher-mapping.txt; done

     ossl_null_ciphers='NULL:eNULL'
     null_ciphers="c0,10, c0,06, c0,15, c0,0b, c0,01, c0,3b, c0,3a, c0,39, 00,b9, 00,b8, 00,b5, 00,b4, 00,2e, 00,2d, 00,b1, 00,b0, 00,2c, 00,3b, 00,02, 00,01, 00,82, 00,83, ff,87, 00,ff"
     sslv2_null_ciphers="FF,80,10, 00,00,00"

     ossl_anon_ciphers='aNULL:ADH'
     anon_ciphers="c0,19, 00,a7, 00,6d, 00,3a, 00,c5, 00,89, c0,47, c0,5b, c0,85, c0,18, 00,a6, 00,6c, 00,34, 00,bf, 00,9b, 00,46, c0,46, c0,5a, c0,84, c0,16, 00,18, c0,17, 00,1b, 00,1a, 00,19, 00,17, c0,15, 00,ff"
     sslv2_anon_ciphers="FF,80,10"

     ossl_exp_ciphers='EXPORT:!ADH:!NULL'
     # grep -i EXP etc/cipher-mapping.txt
     exp_ciphers="00,63, 00,62, 00,61, 00,65, 00,64, 00,60, 00,14, 00,11, 00,19, 00,08, 00,06, 00,27, 00,26, 00,2a, 00,29, 00,0b, 00,0e, 00,17, 00,03, 00,28, 00,2b, 00,ff"
     sslv2_exp_ciphers="04,00,80, 02,00,80, 00,00,00"

     ossl_low_ciphers='LOW:DES:RC2:RC4:MD5:!ADH:!EXP:!NULL:!eNULL:!AECDH'
     # egrep -w '64|56|RC2|RC4|MD5' etc/cipher-mapping.txt | egrep -v 'Au=None|export'
     low_ciphers="00,04, 00,05, 00,09, 00,0C, 00,0F, 00,12, 00,15, 00,1E, 00,20, 00,22, 00, 23, 00,24, 00,25, 00,66, 00,8A, 00,8E, 00,92, C0,02, C0,07, C0,0C, C0,11, C0,33, FF,00, FE,FE, FF,E1, 00,FF"
     sslv2_low_ciphers="01,00,80, 03,00,80, 05,00,80, 06,00,40, 06,01,40, 07,00,c0, 08,00,80, FF,80,00"

     ossl_tdes_ciphers='3DES:IDEA:!aNULL:!ADH:!MD5'
     # egrep -w '3DES|IDEA' etc/cipher-mapping.txt | egrep -v "Au=None|MD5"
     tdes_ciphers="00,07, 00,0A, 00,0D, 00,10, 00,13, 00,16, 00,1F, 00,21, 00,8B, 00,8F, 00,93, C0,03, C0,08, C0,0D, C0,12, C0,1A, C0,1B, C0,1C, C0,34, FE,FF, FF,E0, 00,FF"
     sslv2_tdes_ciphers="07,01,c0"

     # # Now all AES, CAMELLIA, ARIA and SEED CBC ciphers plus GOST
     ossl_obsoleted_ciphers='HIGH:MEDIUM:AES:CAMELLIA:ARIA:!IDEA:!CHACHA20:!3DES:!RC2:!RC4:!AESCCM8:!AESCCM:!AESGCM:!ARIAGCM:!aNULL:!MD5'
     # egrep -w "256|128" etc/cipher-mapping.txt | egrep -v "Au=None|AEAD|RC2|RC4|IDEA|MD5"
     obsoleted_ciphers="00,2F, 00,30, 00,31, 00,32, 00,33, 00,35, 00,36, 00,37, 00,38, 00,39, 00,3C, 00,3D, 00,3E, 00,3F, 00,40, 00,41, 00,42, 00,43, 00,44, 00,45, 00,67, 00,68, 00,69, 00,6A, 00,6B, 00,84, 00,85, 00,86, 00,87, 00,88, 00,8C, 00,8D, 00,90, 00,91, 00,94, 00,95, 00,96, 00,97, 00,98, 00,99, 00,9A, 00,AE, 00,AF, 00,B2, 00,B3, 00,B6, 00,B7, 00,BA, 00,BB, 00,BC, 00,BD, 00,BE, 00,C0, 00,C1, 00,C2, 00,C3, 00,C4, C0,04, C0,05, C0,09, C0,0A, C0,0E, C0,0F, C0,13, C0,14, C0,1D, C0,1E, C0,1F, C0,20, C0,21, C0,22, C0,23, C0,24, C0,25, C0,26, C0,27, C0,28, C0,29, C0,2A, C0,35, C0,36, C0,37, C0,38, C0,3C, C0,3D, C0,3E, C0,3F, C0,40, C0,41, C0,42, C0,43, C0,44, C0,45, C0,48, C0,49, C0,4A, C0,4B, C0,4C, C0,4D, C0,4E, C0,4F, C0,64, C0,65, C0,66, C0,67, C0,68, C0,69, C0,70, C0,71, C0,72, C0,73, C0,74, C0,75, C0,76, C0,77, C0,78, C0,79, C0,94, C0,95, C0,96, C0,97, C0,98, C0,99, C0,9A, C0,9B"
     # Workaround: If we use sockets and in order not to hit 131+1 ciphers we omit the GOST ciphers if SERVER_SIZE_LIMIT_BUG is true.
     # This won't be supported by Cisco ACE anyway.
     "$SERVER_SIZE_LIMIT_BUG" || obsoleted_ciphers="${obsoleted_ciphers}, 00,80, 00,81, FF,01, FF,02, FF,03, FF,85"
     obsoleted_ciphers="${obsoleted_ciphers}, 00,FF"

     ossl_good_ciphers='AESGCM:CHACHA20:CamelliaGCM:AESCCM:ARIAGCM:!kEECDH:!kEDH:!kDHE:!kDHEPSK:!kECDHEPSK:!aNULL'
     # grep AEAD etc/cipher-mapping.txt | egrep -v 'Au=None|TLS_ECDHE|TLS_DHE|TLS_PSK_DHE|TLSv1.3'
     good_ciphers="00,9C, 00,9D, 00,A0, 00,A1, 00,A4, 00,A5, 00,A8, 00,A9, 00,AC, 00,AD, C0,2D, C0,2E, C0,31, C0,32, C0,50, C0,51, C0,54, C0,55, C0,58, C0,59, C0,5E, C0,5F, C0,62, C0,63, C0,6A, C0,6B, C0,6E, C0,6F, C0,7A, C0,7B, C0,7E, C0,7F, C0,82, C0,83, C0,88, C0,89, C0,8C, C0,8D, C0,8E, C0,8F, C0,92, C0,93, C0,9C, C0,9D, C0,A0, C0,A1, C0,A4, C0,A5, C0,A8, C0,A9, CC,AB, CC,AE, 00,FF"

     ossl_strong_ciphers='AESGCM:CHACHA20:CamelliaGCM:AESCCM:ARIAGCM:!kPSK:!kRSAPSK:!kRSA:!kDH:!kECDH:!aNULL'
     # grep AEAD etc/cipher-mapping.txt | egrep 'TLS_ECDHE|TLS_DHE|TLS_PSK_DHE|TLSv1.3'
     strong_ciphers="00,9E, 00,9F, 00,A2, 00,A3, 00,AA, 00,AB, 13,01, 13,02, 13,03, 13,04, 13,05, 16,B7, 16,B8, 16,B9, 16,BA, C0,2B, C0,2C, C0,2F, C0,30, C0,52, C0,53, C0,56, C0,57, C0,5C, C0,5D, C0,60, C0,61, C0,6C, C0,6D, C0,7C, C0,7D, C0,80, C0,81, C0,86, C0,87, C0,8A, C0,8B, C0,90, C0,91, C0,9E, C0,9F, C0,A2, C0,A3, C0,A6, C0,A7, C0,AA, C0,AB, C0,AC, C0,AD, C0,AE, C0,AF, CC,13, CC,14, CC,15, CC,A8, CC,A9, CC,AA, CC,AC, CC,AD, 00,FF"

     # argv[1]: non-TLSv1.3 cipher list to test in OpenSSL syntax
     # argv[2]: TLSv1.3 cipher list to test in OpenSSL syntax
     # argv[3]: string on console / HTML or "finding"
     # argv[4]: rating whether ok to offer
     # argv[5]: string to be appended for fileout
     # argv[6]: non-SSLv2 cipher list to test (hexcodes), if using sockets
     # argv[7]: SSLv2 cipher list to test (hexcodes), if using sockets
     # argv[8]: true if using sockets, false if not
     # argv[9]: CVE
     # argv[10]: CWE

     sub_cipherlists "$ossl_null_ciphers"      "" " NULL ciphers (no encryption)                    "     1 "NULL"      "$null_ciphers"    "$sslv2_null_ciphers"   "$using_sockets" "$cve" "$cwe"
     ret=$?
     sub_cipherlists "$ossl_anon_ciphers"      "" " Anonymous NULL Ciphers (no authentication)      "     1 "aNULL"     "$anon_ciphers"    "$sslv2_anon_ciphers"   "$using_sockets" "$cve" "$cwe"
     ret=$((ret + $?))
     sub_cipherlists "$ossl_exp_ciphers"       "" " Export ciphers (w/o ADH+NULL)                   "     1 "EXPORT"    "$exp_ciphers"     "$sslv2_exp_ciphers"    "$using_sockets" "$cve" "$cwe"
     ret=$((ret + $?))
     sub_cipherlists "$ossl_low_ciphers"       "" " LOW: 64 Bit + DES, RC[2,4], MD5 (w/o export)    "     2 "LOW"       "$low_ciphers"     "$sslv2_low_ciphers"    "$using_sockets" "$cve" "$cwe"
     ret=$((ret + $?))
     sub_cipherlists "$ossl_tdes_ciphers"      "" " Triple DES Ciphers / IDEA                       "     3 "3DES_IDEA" "$tdes_ciphers"    "$sslv2_tdes_ciphers"   "$using_sockets" "$cve" "$cwe2"
     ret=$((ret + $?))
     sub_cipherlists "$ossl_obsoleted_ciphers" "" " Obsoleted CBC ciphers (AES, ARIA etc.)          "     4 "AVERAGE"   "$obsoleted_ciphers"  ""                   "$using_sockets" "$cve" "$cwe2"
     ret=$((ret + $?))
     sub_cipherlists "$ossl_good_ciphers"      "" " Strong encryption (AEAD ciphers) with no FS     "     6 "GOOD"      "$good_ciphers"     ""                     "$using_sockets" ""      ""
     ret=$((ret + $?))
     sub_cipherlists "$ossl_strong_ciphers" 'ALL' " Forward Secrecy strong encryption (AEAD ciphers)"    7 "STRONG"     "$strong_ciphers"   ""                     "$using_sockets" ""      ""
     ret=$((ret + $?))

     outln
     return $ret
}

# The return value is an indicator of the quality of the DH key length in $1:
#   1 = pr_svrty_critical, 2 = pr_svrty_high, 3 = pr_svrty_medium, 4 = pr_svrty_low
#   5 = neither good nor bad, 6 = pr_svrty_good, 7 = pr_svrty_best
pr_dh_quality() {
     local bits="$1"
     local string="$2"

     if [[ "$bits" -le 600 ]]; then
          pr_svrty_critical "$string"
          return 1
     elif [[ "$bits" -le 800 ]]; then
          pr_svrty_high "$string"
          return 2
     elif [[ "$bits" -le 1280 ]]; then
          pr_svrty_medium "$string"
          return 3
     elif [[ "$bits" -ge 2048 ]]; then
          pr_svrty_good "$string"
          return 6
     else
          out "$string"
          return 5
     fi
}

# prints out dh group=prime and in round brackets DH bits and labels it accordingly
# arg1: name of dh group, arg2=bit length
pr_dh() {
     local -i quality=0

     pr_italic "$1"
     out " ("
     pr_dh_quality "$2" "$2 bits"
     quality=$?
     out ")"
     return $quality
}

pr_ecdh_quality() {
     local bits="$1"
     local string="$2"

     if [[ "$bits" -le 80 ]]; then      # has that ever existed?
          pr_svrty_critical "$string"
     elif [[ "$bits" -le 108 ]]; then   # has that ever existed?
          pr_svrty_high "$string"
     elif [[ "$bits" -le 163 ]]; then
          pr_svrty_medium "$string"
     elif [[ "$bits" -le 193 ]]; then   # hmm, according to https://wiki.openssl.org/index.php/Elliptic_Curve_Cryptography it should ok
          pr_svrty_low "$string"        # but openssl removed it https://github.com/drwetter/testssl.sh/issues/299#issuecomment-220905416
     elif [[ "$bits" -le 224 ]]; then
          out "$string"
     elif [[ "$bits" -gt 224 ]]; then
          pr_svrty_good "$string"
     else
          out "$string"
     fi
}

pr_ecdh_curve_quality() {
     curve="$1"
     local -i bits=0

     case "$curve" in
          "sect163k1") bits=163  ;;
          "sect163r1") bits=162  ;;
          "sect163r2") bits=163  ;;
          "sect193r1") bits=193  ;;
          "sect193r2") bits=193  ;;
          "sect233k1") bits=232  ;;
          "sect233r1") bits=233  ;;
          "sect239k1") bits=238  ;;
          "sect283k1") bits=281  ;;
          "sect283r1") bits=282  ;;
          "sect409k1") bits=407 ;;
          "sect409r1") bits=409  ;;
          "sect571k1") bits=570  ;;
          "sect571r1") bits=570  ;;
          "secp160k1") bits=161  ;;
          "secp160r1") bits=161  ;;
          "secp160r2") bits=161  ;;
          "secp192k1") bits=192  ;;
          "prime192v1") bits=192  ;;
          "secp224k1") bits=225  ;;
          "secp224r1") bits=224  ;;
          "secp256k1") bits=256  ;;
          "prime256v1") bits=256  ;;
          "secp384r1") bits=384  ;;
          "secp521r1") bits=521  ;;
          "brainpoolP256r1") bits=256  ;;
          "brainpoolP384r1") bits=384  ;;
          "brainpoolP512r1") bits=512  ;;
          "X25519") bits=253  ;;
          "X448") bits=448  ;;
     esac
     pr_ecdh_quality "$bits" "$curve"
}

# Print $2 based on the quality of the cipher in $1. If $2 is empty, just print $1.
# The return value is an indicator of the quality of the cipher in $1:
#   0 = $1 is empty
#   1 = pr_svrty_critical, 2 = pr_svrty_high, 3 = pr_svrty_medium, 4 = pr_svrty_low
#   5 = neither good nor bad, 6 = pr_svrty_good, 7 = pr_svrty_best
#
# Please note this section isn't particular spot on. It needs to be reconsidered/redone
# SHA1, SSLv3 ciphers are some points which need to be considered.
# Hint: find out by "grep <pattern> etc/cipher-mapping.txt" but it' might be be easier
# to look out Enc= and Au= or Mac=
#
pr_cipher_quality() {
     local cipher="$1"
     local text="$2"
     local ossl_cipher

     [[ -z "$1" ]] && return 0
     [[ -z "$text" ]] && text="$cipher"

     if [[ "$cipher" != TLS_* ]] && [[ "$cipher" != SSL_* ]]; then
          # This must be the OpenSSL name for a cipher or for TLS 1.3 ($TLS13_OSSL_CIPHERS)
          # We can ignore them however as the OpenSSL and RFC names currently match
          if [[ $TLS_NR_CIPHERS -eq 0 ]]; then
               # We have an OpenSSL name and can't convert it to the RFC name which is rarely
               # the case, see "prepare_arrays()" and "./etc/cipher-mapping.txt"
               case "$cipher" in
                    *NULL*|EXP*|ADH*|AECDH*|*anon*)
                         pr_svrty_critical "$text"
                         return 1
                         ;;
                    *RC4*|*RC2*|*MD5|*M1)
                         pr_svrty_high "$text"
                         return 2
                         ;;
                    AES256-GCM-SHA384|AES128-GCM-SHA256|AES256-CCM*|AES128-CCM*|ARIA256-GCM-SHA384|ARIA128-GCM-SHA256)
                         # RSA kx and e.g. GCM isn't certainly the best
                         pr_svrty_good "$text"
                         return 6
                         ;;
                    *CBC3*|*3DES*|*IDEA*)
                         pr_svrty_medium "$text"
                         return 3
                         ;;
                    *DES*)
                         pr_svrty_high "$text"
                         return 2
                         ;;
                    PSK-*GCM*|PSK-*CCM*|RSA-PSK-*GCM*|RSA-PSK-CHACHA20-POLY1305|PSK-CHACHA20-POLY1305)
                         # PSK kx and e.g. GCM isn't certainly the best
                         pr_svrty_good "$text"
                         return 6
                         ;;
                    DH-*GCM*|ECDH-*GCM*)
                         # static DH or ECDH kx and GCM isn't certainly the best
                         pr_svrty_good "$text"
                         return 6
                         ;;
                    *GCM*|*CCM*|*CHACHA20*)
                         pr_svrty_best "$text"
                         return 7
                         ;; #best ones
                    *AES*SHA*|*CAMELLIA*SHA*|*SEED*SHA*|*CBC*|*GOST*)
                         pr_svrty_low "$text"
                         return 4
                         ;;
                    *)
                         out "$text"
                         return 5
                         ;;
               esac
          fi
          ossl_cipher="$cipher"
          cipher="$(openssl2rfc "$cipher")"
          [[ -z "$cipher" ]] && cipher="$ossl_cipher"
     fi

     # Now we look at the RFC cipher names. The sequence matters - as above.
     case "$cipher" in
          *NULL*|*EXP*|*_DES40_*|*anon*)
               pr_svrty_critical "$text"
               return 1
               ;;
          *RC4*|*RC2*|*MD5|*MD5_1)
               pr_svrty_high "$text"
               return 2
               ;;
          *_DES_*)
               if [[ "$cipher" =~ EDE3 ]]; then
                    pr_svrty_medium "$text"  # 3DES
                    return 3
               fi
               pr_svrty_high "$text"
               return 2
               ;;
          *CBC3*|*3DES*|*IDEA*)
               pr_svrty_medium "$text"
               return 3
               ;;
          *CBC*|*GOST*)
               pr_svrty_low "$text"
               return 4
               ;;
          TLS_RSA_*|TLS_DH_*|TLS_ECDH_*|TLS_PSK_WITH_*)
               pr_svrty_good "$text"
               # RSA, or static DH, ECDH, or PSK kx and e.g. GCM isn't certainly the best
               return 6
               ;;
          *GCM*|*CCM*|*CHACHA20*)
               pr_svrty_best "$text"
               return 7
               ;;
          *)
               out "$text"
               return 5
               ;;
     esac
}

# arg1: file with input for grepping the type of ephemeral DH key (DH ECDH)
read_dhtype_from_file() {
     local temp kx

     temp=$(awk -F': ' '/^Server Temp Key/ { print $2 }' "$1")        # extract line
     kx="Kx=${temp%%,*}"
     [[ "$kx" == "Kx=X25519" ]] && kx="Kx=ECDH"
     [[ "$kx" == "Kx=X448" ]] && kx="Kx=ECDH"
     tm_out "$kx"
     return 0
}

# arg1: certificate file
read_sigalg_from_file() {
     $OPENSSL x509 -noout -text -in "$1" 2>/dev/null | awk -F':' '/Signature Algorithm/ { print $2; exit; }'
}


# arg1: file with input for grepping the bit length for ECDH/DHE
# arg2: whether to print warning "old fart" or not (empty: no)
read_dhbits_from_file() {
     local bits what_dh temp curve=""
     local add=""
     local old_fart=" (your $OPENSSL cannot show DH bits)"

     temp=$(awk -F': ' '/^Server Temp Key/ { print $2 }' "$1")        # extract line
     what_dh="${temp%%,*}"
     bits="${temp##*, }"
     curve="${temp#*, }"
     if [[ "$curve" == "$bits" ]]; then
          curve=""
     else
          curve="${curve%%,*}"
     fi
     bits="${bits/bits/}"
     bits="${bits// /}"

     if [[ "$what_dh" == X25519 ]] || [[ "$what_dh" == X448 ]]; then
          curve="$what_dh"
          what_dh="ECDH"
     fi
     if [[ -z "$2" ]]; then
          if [[ -n "$curve" ]]; then
               debugme echo ">$HAS_DH_BITS|$what_dh($curve)|$bits<"
          else
               debugme echo ">$HAS_DH_BITS|$what_dh|$bits<"
          fi
     fi
     [[ -n "$what_dh" ]] && HAS_DH_BITS=true                            # FIX 190
     if [[ -z "$what_dh" ]] && ! "$HAS_DH_BITS"; then
          if [[ "$2" == "string" ]]; then
               tm_out "$old_fart"
          elif [[ -z "$2" ]]; then
               pr_warning "$old_fart"
          fi
          return 0
     fi
     if [[ "$2" == quiet ]]; then
          tm_out "$bits"
          return 0
     fi
     [[ -z "$2" ]] && [[ -n "$bits" ]] && out ", "
     if [[ $what_dh == DH ]] || [[ $what_dh == EDH ]]; then
          add="bit DH"
          [[ -n "$curve" ]] && add+=" ($curve)"
          if [[ "$2" == string ]]; then
               tm_out ", $bits $add"
          else
               pr_dh_quality "$bits" "$bits $add"
          fi
     # https://wiki.openssl.org/index.php/Elliptic_Curve_Cryptography, https://www.keylength.com/en/compare/
     elif [[ $what_dh == ECDH ]]; then
          add="bit ECDH"
          [[ -n "$curve" ]] && add+=" ($curve)"
          if [[ "$2" == string ]]; then
               tm_out ", $bits $add"
          else
               pr_ecdh_quality "$bits" "$bits $add"
          fi
     fi
     return 0
}


# arg1: ID or empty. If empty resumption by ticket will be tested, otherwise by ID
# return: 0: it has resumption, 1:nope, 2: nope (OpenSSL 1.1.1),  6: CLIENT_AUTH --> problem for resumption, 7: can't tell
#
# This is basically a short(?) version from Bulletproof SSL and TLS (p386). The version according to that would be e.g.
#     echo | $OPENSSL s_client -connect testssl.sh:443 -servername testssl.sh -no_ssl2            -reconnect 2>&1 | grep -E 'New|Reused'
#     echo | $OPENSSL s_client -connect testssl.sh:443 -servername testssl.sh -no_ssl2 -no_ticket -reconnect 2>&1 | grep -E 'New|Reused|Session-ID'
#
# FIXME: actually Ivan's version seems faster. Worth to check and since when -reconnect is a/v
#
sub_session_resumption() {
     local ret ret1 ret2
     local tmpfile=$(mktemp $TEMPDIR/session_resumption.$NODEIP.XXXXXX)
     local sess_data=$(mktemp $TEMPDIR/sub_session_data_resumption.$NODEIP.XXXXXX)
     local -a rw_line
     local not_new_reused=false
     local protocol="$1"

     if [[ "$2" == ID ]]; then
          local byID=true
          local addcmd="-no_ticket"
     else
          local byID=false
          local addcmd=""
          if ! "$TLS_TICKETS"; then
               return 1
          fi
     fi
     "$CLIENT_AUTH" && return 6
     if ! "$HAS_TLS13" && "$HAS_NO_SSL2"; then
          addcmd+=" -no_ssl2"
     else
          protocol=${protocol/\./_}
          protocol=${protocol/v/}
          protocol="-$(tolower $protocol)"
          # In some cases a server will not support session tickets, but will support session resumption
          # by ID. In such a case, it may be more likely to support session resumption with TLSv1.2 than
          # with TLSv1.3. So, if testing a server that does not support session tickets and that supports
          # both TLSv1.3 and TLSv1.2 for session resumption by ID, then use a TLSv1.2 ClientHello. (Note that
          # the line below assumes that if $protocol is -tls1_3, then the server either supports TLSv1.2 or
          # is TLSv1.3-only.
          ! "$TLS_TICKETS" && "$byID" && [[ $(has_server_protocol "tls1_2") -eq 0 ]] && protocol="-tls1_2"
          addcmd+=" $protocol"
     fi

     $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI $addcmd -sess_out $sess_data") </dev/null &>/dev/null
     ret1=$?
     if [[ $ret1 -ne 0 ]]; then
          debugme echo -n "Couldn't connect #1  "
          return 7
     fi
     if "$byID" && [[ ! "$OSSL_NAME" =~ LibreSSL ]] && \
        ( [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 1.1.1* ]] || [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 3.0.0* ]] ) && \
        [[ ! -s "$sess_data" ]]; then
          # it seems OpenSSL indicates no Session ID resumption by just not generating output
          debugme echo -n "No session resumption byID (empty file)"
          ret=2
     else
          $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI $addcmd -sess_in $sess_data") </dev/null >$tmpfile 2>$ERRFILE
          ret2=$?
          if [[ $DEBUG -ge 2 ]]; then
               echo -n "$ret1, $ret2, "
               [[ -s "$sess_data" ]] && echo "not empty" || echo "empty"
          fi
          if [[ $ret2 -ne 0 ]]; then
               debugme echo -n "Couldn't connect #2  "
               return 7
          fi
          # "Reused" indicates session material was reused, "New": not
          if grep -aq "^Reused" "$tmpfile"; then
               new_sid=false
          elif grep -aq "^New" "$tmpfile"; then
               new_sid=true
          else
               debugme echo -n "Problem with 2nd ServerHello  "
               not_new_reused=true
          fi
          # Now get the line and compare the numbers "read" and "written" as a second criteria.
          # If the "read" number is bigger: a new session ID was probably used
          rw_line="$(awk '/^SSL handshake has read/ { print $5" "$(NF-1) }' "$tmpfile" )"
          rw_line=($rw_line)
          if [[ "${rw_line[0]}" -gt "${rw_line[1]}" ]]; then
               new_sid2=true
          else
               new_sid2=false
          fi
          debugme echo "${rw_line[0]}, ${rw_line[1]}"

          if "$new_sid2" && "$new_sid"; then
               debugme echo -n "No session resumption "
               ret=1
          elif ! "$new_sid2" && ! "$new_sid"; then
               debugme echo -n "Session resumption "
               ret=0
          else
               debugme echo -n "unclear status: $ret1, $ret2, $new_sid, $new_sid2  -- "
               ret=5
          fi
          if [[ $DEBUG -ge 2 ]]; then
               "$byID" && echo "byID" || echo "by ticket"
          fi
     fi
     "$byID" && \
          tmpfile_handle ${FUNCNAME[0]}.byID.log $tmpfile || \
          tmpfile_handle ${FUNCNAME[0]}.byticket.log $tmpfile
     return $ret
}

run_server_preference() {
     local cipher1="" cipher2="" tls13_cipher1="" tls13_cipher2="" default_proto=""
     local default_cipher=""
     local limitedsense="" supported_sslv2_ciphers
     local -a offered_cipher offered_proto
     local proto_ossl proto_txt proto_hex cipherlist i
     local -i ret=0 j sclient_success str_len
     local list_fwd="DHE-RSA-SEED-SHA:SEED-SHA:DES-CBC3-SHA:RC4-MD5:DES-CBC-SHA:RC4-SHA:AES128-SHA:AES128-SHA256:AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-RSA-AES128-SHA:ECDH-RSA-AES256-SHA:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:DHE-DSS-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:AES256-SHA256:ECDHE-RSA-DES-CBC3-SHA:ECDHE-RSA-AES128-SHA256:AES256-GCM-SHA384:AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-SHA256:ADH-AES256-GCM-SHA384:AECDH-AES128-SHA:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-AES128-SHA"
     local list_reverse="ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-RC4-SHA:AECDH-AES128-SHA:ADH-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-GCM-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-DES-CBC3-SHA:AES256-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDH-RSA-AES256-SHA:ECDH-RSA-AES128-SHA:ECDH-RSA-DES-CBC3-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-AES128-SHA:AES256-SHA:AES128-SHA256:AES128-SHA:RC4-SHA:DES-CBC-SHA:RC4-MD5:DES-CBC3-SHA:SEED-SHA:DHE-RSA-SEED-SHA"
     tls_list_fwd="c0,2c, c0,30, 00,9f, cc,a9, cc,a8, cc,aa, c0,2b, c0,2f, 00,9e, c0,24, c0,28, 00,6b, c0,23, c0,27, 00,67, c0,0a, 00,04, 00,05, 00,09, 00,0a, 00,9a, 00,96,
                   c0,14, 00,39, c0,09, c0,13, 00,33, 00,9d, 00,9c, 13,01, 13,02, 13,03, 13,04, 13,05, 00,3d, 00,3c, 00,35, 00,2f, 00,ff"
     tls_list_rev="00,2f, 00,35, 00,3c, 00,3d, 13,05, 13,04, 13,03, 13,02, 13,01, 00,9c, 00,9d, 00,33, c0,13, c0,09, 00,39, c0,14, 00,96, 00,9a, 00,0a, 00,09, 00,05, 00,04,
                   c0,0a, 00,67, c0,27, c0,23, 00,6b, c0,28, c0,24, 00,9e, c0,2f, c0,2b, cc,aa, cc,a8, cc,a9, 00,9f, c0,30, c0,2c, 00,ff"
     local has_cipher_order=false has_tls13_cipher_order=false
     local addcmd="" addcmd2=""
     local using_sockets=true
     local jsonID="cipher_order"
     local cwe="CWE-310"
     local cve=""

     "$SSL_NATIVE" && using_sockets=false

     outln
     pr_headlineln " Testing server's cipher preferences "

     outln
     pr_bold " Has server cipher order?     "

     if [[ "$OPTIMAL_PROTO" == -ssl2 ]]; then
          addcmd="$OPTIMAL_PROTO"
     else
          # the supplied openssl will send an SSLv2 ClientHello if $SNI is empty
          # and the -no_ssl2 isn't provided.
          addcmd="-no_ssl2 $SNI"
     fi

     # Determine negotiated protocol upfront
     sclient_success=1
     if "$using_sockets" && [[ $(has_server_protocol "tls1_3") -ne 1 ]]; then
          # Send similar list of cipher suites as OpenSSL 1.1.1 does
          tls_sockets "04" \
                      "c0,2c, c0,30, 00,9f, cc,a9, cc,a8, cc,aa, c0,2b, c0,2f, 00,9a, 00,96,
                       00,9e, c0,24, c0,28, 00,6b, c0,23, c0,27, 00,67, c0,0a,
                       c0,14, 00,39, c0,09, c0,13, 00,33, 00,9d, 00,9c, 13,02,
                       13,03, 13,01, 13,04, 13,05, 00,3d, 00,3c, 00,35, 00,2f, 00,ff" \
                      "ephemeralkey"
          sclient_success=$?
          if [[ $sclient_success -eq 0 ]]; then
               add_proto_offered tls1_3 yes
          elif [[ $sclient_success -eq 2 ]]; then
               sclient_success=0           # 2: downgraded
               case $DETECTED_TLS_VERSION in
                    0303) add_proto_offered tls1_2 yes ;;
                    0302) add_proto_offered tls1_1 yes ;;
                    0301) add_proto_offered tls1 yes ;;
                    0300) add_proto_offered ssl3 yes ;;
               esac
          fi
          if [[ $sclient_success -eq 0 ]] ; then
               cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" $TMPFILE
               cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" "$TEMPDIR/$NODEIP.parse_tls13_serverhello.txt"
               cipher0=$(get_cipher $TMPFILE)
          fi
     fi
     if [[ $sclient_success -ne 0 ]]; then
          $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $addcmd") </dev/null 2>>$ERRFILE >$TMPFILE
          if sclient_connect_successful $? $TMPFILE; then
               cipher0=$(get_cipher $TMPFILE)
               debugme tm_out "0 --> $cipher0\n"
               cp $TMPFILE "$TEMPDIR/$NODEIP.parse_tls13_serverhello.txt"
          else
               # 2 second try with $OPTIMAL_PROTO especially for intolerant IIS6 servers:
               $OPENSSL s_client $(s_client_options "$STARTTLS $OPTIMAL_PROTO $BUGS -connect $NODEIP:$PORT $PROXY $SNI") </dev/null 2>>$ERRFILE >$TMPFILE
               if ! sclient_connect_successful $? $TMPFILE; then
                    pr_warning "Handshake error!"
                    ret=1
               fi
          fi
     fi
     default_proto=$(get_protocol $TMPFILE)
     [[ "$default_proto" == TLSv1.0 ]] && default_proto="TLSv1"
     # debugme tm_out " --> $default_proto\n"

     # Some servers don't have a TLS 1.3 cipher order, see #1163
     if [[ "$default_proto" == TLSv1.3 ]]; then
          tls_sockets "04" "13,05, 13,04, 13,03, 13,02, 13,01, 00,ff"
          [[ $? -ne 0 ]] && ret=1 && prln_fixme "something weird happened around line $((LINENO - 1))"
          cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" $TMPFILE
          tls13_cipher1=$(get_cipher $TMPFILE)
          debugme tm_out "TLS 1.3: --> $tls13_cipher1\n"
          tls_sockets "04" "13,01, 13,02, 13,03, 13,04, 13,05, 00,ff"
          [[ $? -ne 0 ]] && ret=1 && prln_fixme "something weird happened around line $((LINENO - 1))"
          cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" $TMPFILE
          tls13_cipher2=$(get_cipher $TMPFILE)
          debugme tm_out "TLS 1.3: --> $tls13_cipher2\n"

          [[ $tls13_cipher1 == $tls13_cipher2 ]] && has_tls13_cipher_order=true
     fi
     # Check whether the server has a cipher order for SSLv3 - TLSv1.2
     if [[ $(has_server_protocol "tls1_2") -ne 0 ]] && [[ $(has_server_protocol "tls1_1") -ne 0 ]] && \
        [[ $(has_server_protocol "tls1") -ne 0 ]] && [[ $(has_server_protocol "ssl3") -ne 0 ]]; then
          # Based on testing performed by determine_optimal_sockets_params(), it is believed that
          # this server does not offer SSLv3 - TLSv1.2.
          has_cipher_order="$has_tls13_cipher_order"
     elif [[ "$OPTIMAL_PROTO" != -ssl2 ]]; then
          if [[ -n "$STARTTLS_OPTIMAL_PROTO" ]]; then
               [[ ! "$STARTTLS_OPTIMAL_PROTO" =~ ssl ]] && addcmd2="$SNI"
               [[ "$STARTTLS_OPTIMAL_PROTO" != -tls1_3 ]] && addcmd2+=" $STARTTLS_OPTIMAL_PROTO"
          else
               addcmd2="-no_ssl2 $SNI"
          fi
          [[ $DEBUG -ge 4 ]] && echo -e "\n Forward: ${list_fwd}"
          $OPENSSL s_client $(s_client_options "$STARTTLS -cipher $list_fwd $BUGS -connect $NODEIP:$PORT $PROXY $addcmd2") </dev/null 2>$ERRFILE >$TMPFILE
          if ! sclient_connect_successful $? $TMPFILE; then
               list_fwd="$(actually_supported_osslciphers $list_fwd '' '-tls1')"
               pr_warning "no matching cipher in this list found (pls report this): "
               outln "$list_fwd  . "
               fileout "$jsonID" "WARN" "Could not determine server cipher order, no matching cipher in list found (pls report this): $list_fwd"
               tmpfile_handle ${FUNCNAME[0]}.txt
               return 1
               # we assume the problem is with testing here but it could be also the server side
          fi
          cipher1=$(get_cipher $TMPFILE)               # cipher1 from 1st serverhello
          debugme tm_out "1 --> $cipher1\n"

          # second client hello with reverse list
          [[ $DEBUG -ge 4 ]] && echo -e "\n Reverse: ${list_reverse}"
          $OPENSSL s_client $(s_client_options "$STARTTLS -cipher $list_reverse $BUGS -connect $NODEIP:$PORT $PROXY $addcmd2") </dev/null 2>>$ERRFILE >$TMPFILE
          # first handshake worked above so no error handling here
          cipher2=$(get_cipher $TMPFILE)               # cipher2 from 2nd serverhello
          debugme tm_out "2 --> $cipher2\n"

          [[ $cipher1 == $cipher2 ]] && has_cipher_order=true
     fi
     debugme echo "has_cipher_order: $has_cipher_order"
     debugme echo "has_tls13_cipher_order: $has_tls13_cipher_order"

     if "$TLS13_ONLY" && ! "$has_tls13_cipher_order"; then
          out "no (TLS 1.3 only)"
          limitedsense=" (limited sense as client will pick)"
          fileout "$jsonID" "INFO" "not a cipher order for TLS 1.3 configured"
     elif ! "$has_cipher_order" && ! "$has_tls13_cipher_order"; then
          # server used the different ends (ciphers) from the client hello
          pr_svrty_high "no (NOT ok)"
          limitedsense=" (limited sense as client will pick)"
          fileout "$jsonID" "HIGH" "NOT a cipher order configured"
     elif "$has_cipher_order" && ! "$has_tls13_cipher_order" && [[ "$default_proto" == TLSv1.3 ]]; then
          pr_svrty_good "yes (OK)"; out " -- only for < TLS 1.3"
          fileout "$jsonID" "OK" "server -- TLS 1.3 client determined"
     elif ! "$has_cipher_order" && "$has_tls13_cipher_order"; then
          pr_svrty_high "no (NOT ok)"; out " -- only for TLS 1.3"
          fileout "$jsonID" "HIGH" "server -- < TLS 1.3 client determined"
     else
          if "$has_tls13_cipher_order"; then
               if "$TLS13_ONLY"; then
                    out "yes (TLS 1.3 only)"
                    fileout "$jsonID" "INFO" "server (TLS 1.3)"
               else
                    pr_svrty_best "yes (OK)"
                    out " -- TLS 1.3 and below"
                    fileout "$jsonID" "OK" "server"
               fi
          else
               # we don't have TLS 1.3 at all
               pr_svrty_best "yes (OK)"
               fileout "$jsonID" "OK" "server"
          fi
     fi
     outln

     pr_bold " Negotiated protocol          "
     jsonID="protocol_negotiated"

     case "$default_proto" in
          *TLSv1.3)
               prln_svrty_best $default_proto
               fileout "$jsonID" "OK" "Default protocol TLS1.3"
               ;;
          *TLSv1.2)
               prln_svrty_best $default_proto
               fileout "$jsonID" "OK" "Default protocol TLS1.2"
               ;;
          *TLSv1.1)
               prln_svrty_low $default_proto
               fileout "$jsonID" "LOW" "Default protocol TLS1.1"
               ;;
          *TLSv1)
               prln_svrty_low $default_proto
               fileout "$jsonID" "LOW" "Default protocol TLS1.0"
               ;;
          *SSLv2)
               prln_svrty_critical $default_proto
               fileout "$jsonID" "CRITICAL" "Default protocol SSLv2"
               ;;
          *SSLv3)
               prln_svrty_critical $default_proto
               fileout "$jsonID" "CRITICAL" "Default protocol SSLv3"
               ;;
          "")
               pr_warning "default proto empty"
               if [[ $OSSL_VER == 1.0.2* ]]; then
                    outln " (Hint: if IIS6 give OpenSSL 1.0.1 a try)"
                    fileout "$jsonID" "WARN" "Default protocol empty (Hint: if IIS6 give OpenSSL 1.0.1 a try)"
               else
                    outln
                    fileout "$jsonID" "WARN" "Default protocol empty"
               fi
               ret=1
               ;;
          *)
               pr_warning "FIXME line $LINENO: $default_proto"
               fileout "$jsonID" "WARN" "FIXME line $LINENO: $default_proto"
               ret=1
               ;;
     esac

     pr_bold " Negotiated cipher            "
     jsonID="cipher_negotiated"

     # restore file from above
     [[ "$default_proto" == TLSv1.3 ]] && cp "$TEMPDIR/$NODEIP.parse_tls13_serverhello.txt" $TMPFILE
     cipher1=$(get_cipher $TMPFILE)

     # Sanity check: Handshake with no ciphers and one with forward list didn't overlap
     if [[ "$cipher0" != $cipher1 ]]; then
          limitedsense=" (matching cipher in list missing)"
     fi

     if [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]] && ( [[ "$cipher1" == TLS_* ]] || [[ "$cipher1" == SSL_* ]] ); then
          default_cipher="$(rfc2openssl "$cipher1")"
     elif [[ "$DISPLAY_CIPHERNAMES" =~ rfc ]] && [[ "$cipher1" != TLS_* ]] && [[ "$cipher1" != SSL_* ]]; then
          default_cipher="$(openssl2rfc "$cipher1")"
     fi
     [[ -z "$default_cipher" ]] && default_cipher="$cipher1"
     pr_cipher_quality "$default_cipher"
     case $? in
          1)   fileout "$jsonID" "CRITICAL" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense"
               ;;
          2)   fileout "$jsonID" "HIGH" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense"
               ;;
          3)   fileout "$jsonID" "MEDIUM" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense"
               ;;
          6|7) fileout "$jsonID" "OK" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense"
               ;;   # best ones
          4)   fileout "$jsonID" "LOW" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") (cbc) $limitedsense"
               ;;  # it's CBC. --> lucky13
          0)   pr_warning "default cipher empty" ;
               if [[ $OSSL_VER == 1.0.2* ]]; then
                    out " (Hint: if IIS6 give OpenSSL 1.0.1 a try)"
                    fileout "$jsonID" "WARN" "Default cipher empty  (if IIS6 give OpenSSL 1.0.1 a try) $limitedsense"
               else
                    fileout "$jsonID" "WARN" "Default cipher empty $limitedsense"
               fi
               ret=1
               ;;
          *)   fileout "$jsonID" "INFO" "$default_cipher$(read_dhbits_from_file "$TMPFILE" "string") $limitedsense"
               ;;
     esac
     read_dhbits_from_file "$TMPFILE"

     if [[ "$cipher0" != $cipher1 ]]; then
          pr_warning " -- inconclusive test, matching cipher in list missing"
          outln ", better see below"
          #FIXME: This is ugly but the best we can do before rewrite this section
     else
          outln "$limitedsense"
     fi

     "$FAST" && using_sockets=false
     [[ $TLS_NR_CIPHERS == 0 ]] && using_sockets=false

     pr_bold " Cipher per protocol"
     outln "\n" && neat_header
     while read proto_ossl proto_hex proto_txt; do
          pr_underline "$(printf -- "%b" "$proto_txt")"
          # TODO: If there's no cipher we should consider not displaying the text in the round brackets)
          # the following takes care of that but only if we know the protocol is not supported
          if [[ $(has_server_protocol "$proto_ossl") -eq 1 ]]; then
               outln "\n - "
               continue
          fi
          # TODO: Also the fact that a protocol is not supported seems not to be saved by cipher_pref_check()
          # (./testssl.sh --wide -p -P -E  vs ./testssl.sh --wide -P -E )
          if [[ $proto_ossl == ssl2 ]] || \
                    ( [[ $proto_ossl != tls1_3 ]] && ! "$has_cipher_order" ]] ) || \
                    ( [[ $proto_ossl == tls1_3 ]] && ! "$has_tls13_cipher_order" ]] ); then
               if [[ $proto_ossl == ssl2 ]]; then
                   outln " (listed by strength)"
               elif [[ $proto_ossl == tls1_3 ]]; then
                    outln " (no server order, thus listed by strength)"
               else
                    prln_svrty_high " (no server order, thus listed by strength)"
               fi
               ciphers_by_strength "-$proto_ossl" "$proto_hex" "$proto_txt" "$using_sockets" "true"
          else
               outln " (server order)"
               cipher_pref_check "$proto_ossl" "$proto_hex" "$proto_txt" "$using_sockets" "true"
          fi
     done <<< "$(tm_out " ssl2 22 SSLv2\n ssl3 00 SSLv3\n tls1 01 TLSv1\n tls1_1 02 TLSv1.1\n tls1_2 03 TLSv1.2\n tls1_3 04 TLSv1.3\n")"
     outln

     return $ret
     # end of run_server_preference()
}

# arg1: true if the list that is returned does not need to be ordered by preference.
check_tls12_pref() {
     local unordered_list_ok="$1"
     local batchremoved="-CAMELLIA:-IDEA:-KRB5:-PSK:-SRP:-aNULL:-eNULL"
     local batchremoved_success=false
     local tested_cipher="" cipher ciphers_to_test
     local order=""
     local -i nr_ciphers_found_r1=0 nr_ciphers_found_r2=0

     while true; do
          $OPENSSL s_client $(s_client_options "$STARTTLS -tls1_2 $BUGS -cipher "ALL$tested_cipher:$batchremoved" -connect $NODEIP:$PORT $PROXY $SNI") </dev/null 2>>$ERRFILE >$TMPFILE
          if sclient_connect_successful $? $TMPFILE ; then
               cipher=$(get_cipher $TMPFILE)
               order+=" $cipher"
               tested_cipher="$tested_cipher:-$cipher"
               nr_ciphers_found_r1+=1
               "$FAST" && break
          else
               debugme tmln_out "A: $tested_cipher"
               break
          fi
     done
     batchremoved="${batchremoved//-/}"
     while true; do
          # no ciphers from "ALL$tested_cipher:$batchremoved" left
          # now we check $batchremoved, and remove the minus signs first:
          $OPENSSL s_client $(s_client_options "$STARTTLS -tls1_2 $BUGS -cipher "$batchremoved" -connect $NODEIP:$PORT $PROXY $SNI") </dev/null 2>>$ERRFILE >$TMPFILE
          if sclient_connect_successful $? $TMPFILE ; then
               batchremoved_success=true               # signals that we have some of those ciphers and need to put everything together later on
               cipher=$(get_cipher $TMPFILE)
               order+=" $cipher"
               batchremoved="$batchremoved:-$cipher"
               nr_ciphers_found_r1+=1
               debugme tmln_out "B1: $batchremoved"
               "$FAST" && break
          else
               debugme tmln_out "B2: $batchremoved"
               break
               # nothing left with batchremoved ciphers, we need to put everything together
          fi
     done

     if "$batchremoved_success" && ! "$unordered_list_ok"; then
          # now we combine the two cipher sets from both while loops
          combined_ciphers="$order"
          order="" ; tested_cipher=""
          while true; do
               ciphers_to_test=""
               for cipher in $combined_ciphers; do
                    [[ ! "$tested_cipher:" =~ :-$cipher: ]] && ciphers_to_test+=":$cipher"
               done
               [[ -z "$ciphers_to_test" ]] && break
               $OPENSSL s_client $(s_client_options "$STARTTLS -tls1_2 $BUGS -cipher "${ciphers_to_test:1}" -connect $NODEIP:$PORT $PROXY $SNI") </dev/null 2>>$ERRFILE >$TMPFILE
               if sclient_connect_successful $? $TMPFILE ; then
                    cipher=$(get_cipher $TMPFILE)
                    order+=" $cipher"
                    tested_cipher="$tested_cipher:-$cipher"
                    nr_ciphers_found_r2+=1
                    "$FAST" && break
               else
                    # This shouldn't happen.
                    break
               fi
          done
          if "$FAST" && [[ $nr_ciphers_found_r2 -ne 1 ]]; then
               prln_fixme "something weird happened around line $((LINENO - 14))"
               return 1
          elif ! "$FAST" && [[ $nr_ciphers_found_r2 -ne $nr_ciphers_found_r1 ]]; then
               prln_fixme "something weird happened around line $((LINENO - 16))"
               return 1
          fi
     fi
     tm_out "$order"

     tmpfile_handle ${FUNCNAME[0]}.txt
     return 0
}

# At the moment only called from run_server_preference()
cipher_pref_check() {
     local proto="$1" proto_hex="$2" proto_text="$3"
     local using_sockets="$4"
     local wide="$5"          # at the moment always = true
     local tested_cipher cipher order rfc_cipher rfc_order
     local overflow_probe_cipherlist="ALL:-ECDHE-RSA-AES256-GCM-SHA384:-AES128-SHA:-DES-CBC3-SHA"
     local -i i nr_ciphers nr_nonossl_ciphers num_bundles bundle_size bundle end_of_bundle success
     local -i nr_ciphers_found
     local hexc ciphers_to_test
     local -a normalized_hexcode ciph kx enc export2 sigalg
     local -a rfc_ciph hexcode ciphers_found="" ciphers_found2
     local -a -i index
     local ciphers_found_with_sockets

     order=""; ciphers_found_with_sockets=false
     if [[ $proto == ssl3 ]] && ! "$HAS_SSL3" && ! "$using_sockets"; then
          prln_local_problem "$OPENSSL doesn't support \"s_client -ssl3\"";
          return 0
     fi
     if [[ $proto == tls1_3 ]] && ! "$HAS_TLS13" && ! "$using_sockets"; then
          prln_local_problem "$OPENSSL doesn't support \"s_client -tls1_3\"";
          return 0
     fi

     if ( [[ $proto != tls1_3 ]] || "$HAS_TLS13" ) && ( [[ $proto != ssl3 ]] || "$HAS_SSL3" ); then
          if [[ $proto == tls1_2 ]] && "$SERVER_SIZE_LIMIT_BUG"; then
               order="$(check_tls12_pref "$wide")"
               [[ "${order:0:1}" == \  ]] && order="${order:1}"
               ciphers_found="$order"
          fi
          if "$wide" || [[ -z "$order" ]]; then
               tested_cipher=""; order=""; nr_ciphers_found=0
               while true; do
                    if [[ $proto != tls1_3 ]]; then
                         if [[ -n "$ciphers_found" ]]; then
                                  ciphers_to_test=""
                                  for cipher in $ciphers_found; do
                                       [[ ! "$tested_cipher:" =~ :-$cipher: ]] && ciphers_to_test+=":$cipher"
                                  done
                                  [[ -z "$ciphers_to_test" ]] && break
                                  ciphers_to_test="-cipher ${ciphers_to_test:1}"
                         else
                              ciphers_to_test="-cipher ALL:COMPLEMENTOFALL${tested_cipher}"
                         fi
                    else
                         ciphers_to_test=""
                         for cipher in $(colon_to_spaces "$TLS13_OSSL_CIPHERS"); do
                              [[ ! "$tested_cipher:" =~ :-$cipher: ]] && ciphers_to_test+=":$cipher"
                         done
                         [[ -z "$ciphers_to_test" ]] && break
                         ciphers_to_test="-ciphersuites ${ciphers_to_test:1}"
                    fi
                    $OPENSSL s_client $(s_client_options "$STARTTLS -"$proto" $BUGS $ciphers_to_test -connect $NODEIP:$PORT $PROXY $SNI") </dev/null 2>>$ERRFILE >$TMPFILE
                    sclient_connect_successful $? $TMPFILE || break
                    cipher=$(get_cipher $TMPFILE)
                    [[ -z "$cipher" ]] && break
                    order+="$cipher "
                    tested_cipher+=":-"$cipher
                    "$FAST" && break
                    if "$wide"; then
                         for (( i=0; i < TLS_NR_CIPHERS; i++ )); do
                              [[ "$cipher" == ${TLS_CIPHER_OSSL_NAME[i]} ]] && break
                         done
                         [[ $i -eq $TLS_NR_CIPHERS ]] && continue
                         normalized_hexcode[nr_ciphers_found]="$(normalize_ciphercode "${TLS_CIPHER_HEXCODE[i]}")"
                         ciph[nr_ciphers_found]="${TLS_CIPHER_OSSL_NAME[i]}"
                         kx[nr_ciphers_found]="${TLS_CIPHER_KX[i]}"
                         [[ $proto == tls1_3 ]] && kx[nr_ciphers_found]="$(read_dhtype_from_file $TMPFILE)"
                         if ( [[ ${kx[nr_ciphers_found]} == Kx=ECDH ]] || [[ ${kx[nr_ciphers_found]} == Kx=DH ]] || [[ ${kx[nr_ciphers_found]} == Kx=EDH ]] ); then
                              kx[nr_ciphers_found]+=" $(read_dhbits_from_file "$TMPFILE" quiet)"
                         fi
                         enc[nr_ciphers_found]="${TLS_CIPHER_ENC[i]}"
                         export2[nr_ciphers_found]="${TLS_CIPHER_EXPORT[i]}"
                         sigalg[nr_ciphers_found]=""
                         "$SHOW_SIGALGO" && grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TMPFILE && \
                              sigalg[nr_ciphers_found]="$(read_sigalg_from_file "$TMPFILE")"
                         nr_ciphers_found+=1
                    fi
               done
          fi
     fi

     nr_nonossl_ciphers=0
     if "$using_sockets"; then
          for (( i=0; i < TLS_NR_CIPHERS; i++ )); do
               ciphers_found[i]=false
               hexc="${TLS_CIPHER_HEXCODE[i]}"
               if [[ ${#hexc} -eq 9 ]]; then
                    if [[ " $order " =~ " ${TLS_CIPHER_OSSL_NAME[i]} " ]]; then
                         ciphers_found[i]=true
                    else
                         ciphers_found2[nr_nonossl_ciphers]=false
                         hexcode[nr_nonossl_ciphers]="${hexc:2:2},${hexc:7:2}"
                         rfc_ciph[nr_nonossl_ciphers]="${TLS_CIPHER_RFC_NAME[i]}"
                         index[nr_nonossl_ciphers]=$i
                         # Only test ciphers that are relevant to the protocol.
                         if [[ $proto == tls1_3 ]]; then
                              [[ "${hexc:2:2}" == 13 ]] && nr_nonossl_ciphers+=1
                         elif [[ $proto == tls1_2 ]]; then
                              [[ "${hexc:2:2}" != 13 ]] && nr_nonossl_ciphers+=1
                         elif [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA256 ]] && \
                              [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA384 ]] && \
                              [[ "${TLS_CIPHER_RFC_NAME[i]}" != *"_CCM" ]] && \
                              [[ "${TLS_CIPHER_RFC_NAME[i]}" != *"_CCM_8" ]]; then
                              nr_nonossl_ciphers+=1
                         fi
                    fi
               fi
          done
     fi

     if [[ $nr_nonossl_ciphers -eq 0 ]]; then
          num_bundles=0
     elif [[ $proto != tls1_2 ]] || ! "$SERVER_SIZE_LIMIT_BUG"; then
          num_bundles=1
          bundle_size=$nr_nonossl_ciphers
     else
          num_bundles=$nr_nonossl_ciphers/128
          [[ $((nr_nonossl_ciphers%128)) -ne 0 ]] && num_bundles+=1

          bundle_size=$nr_nonossl_ciphers/$num_bundles
          [[ $((nr_nonossl_ciphers%num_bundles)) -ne 0 ]] && bundle_size+=1
     fi

     for (( bundle=0; bundle < num_bundles; bundle++ )); do
          end_of_bundle=$(( (bundle+1)*bundle_size ))
          [[ $end_of_bundle -gt $nr_nonossl_ciphers ]] && end_of_bundle=$nr_nonossl_ciphers
          while true; do
               ciphers_to_test=""
               for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do
                    ! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode[i]}"
               done
               [[ -z "$ciphers_to_test" ]] && break
               tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "ephemeralkey"
               [[ $? -ne 0 ]] && break
               cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")
               for (( i=bundle*bundle_size; i < end_of_bundle; i++ )); do
                    [[ "$cipher" == "${rfc_ciph[i]}" ]] && ciphers_found2[i]=true && break
               done
               i=${index[i]}
               ciphers_found[i]=true
               ciphers_found_with_sockets=true
               if [[ $proto != tls1_2 ]] || ! "$SERVER_SIZE_LIMIT_BUG"; then
                    # Throw out the results found so far and start over using just sockets
                    bundle=$num_bundles
                    for (( i=0; i < TLS_NR_CIPHERS; i++ )); do
                         ciphers_found[i]=true
                    done
                    break
               fi
          done
     done

     # If additional ciphers were found using sockets and there is no
     # SERVER_SIZE_LIMIT_BUG, then just use sockets to find the cipher order.
     # If there is a SERVER_SIZE_LIMIT_BUG, then use sockets to find the cipher
     # order, but starting with the list of ciphers supported by the server.
     if "$ciphers_found_with_sockets"; then
          order=""; nr_ciphers=0; nr_ciphers_found=0
          for (( i=0; i < TLS_NR_CIPHERS; i++ )); do
               hexc="${TLS_CIPHER_HEXCODE[i]}"
               if "${ciphers_found[i]}" && [[ ${#hexc} -eq 9 ]]; then
                    ciphers_found2[nr_ciphers]=false
                    hexcode[nr_ciphers]="${hexc:2:2},${hexc:7:2}"
                    rfc_ciph[nr_ciphers]="${TLS_CIPHER_RFC_NAME[i]}"
                    if [[ $proto == tls1_3 ]]; then
                         [[ "${hexc:2:2}" == "13" ]] && nr_ciphers+=1
                    elif [[ $proto == tls1_2 ]]; then
                         [[ "${hexc:2:2}" != "13" ]] && nr_ciphers+=1
                    elif [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA256 ]] && \
                         [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA384 ]] && \
                         [[ "${TLS_CIPHER_RFC_NAME[i]}" != *"_CCM" ]] && \
                         [[ "${TLS_CIPHER_RFC_NAME[i]}" != *"_CCM_8" ]]; then
                         nr_ciphers+=1
                    fi
               fi
          done
          while true; do
               ciphers_to_test=""
               for (( i=0; i < nr_ciphers; i++ )); do
                    ! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode[i]}"
               done
               [[ -z "$ciphers_to_test" ]] && break
               tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "ephemeralkey"
               [[ $? -ne 0 ]] && break
               cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")
               for (( i=0; i < nr_ciphers; i++ )); do
                    [[ "$cipher" == ${rfc_ciph[i]} ]] && ciphers_found2[i]=true && break
               done
               if "$wide"; then
                    for (( i=0; i < TLS_NR_CIPHERS; i++ )); do
                         [[ "$cipher" == ${TLS_CIPHER_RFC_NAME[i]} ]] && break
                    done
                    [[ $i -eq $TLS_NR_CIPHERS ]] && continue
                    normalized_hexcode[nr_ciphers_found]="$(normalize_ciphercode "${TLS_CIPHER_HEXCODE[i]}")"
                    ciph[nr_ciphers_found]="${TLS_CIPHER_OSSL_NAME[i]}"
                    kx[nr_ciphers_found]="${TLS_CIPHER_KX[i]}"
                    [[ $proto == tls1_3 ]] && kx[nr_ciphers_found]="$(read_dhtype_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")"
                    if ( [[ ${kx[nr_ciphers_found]} == Kx=ECDH ]] || [[ ${kx[nr_ciphers_found]} == Kx=DH ]] || [[ ${kx[nr_ciphers_found]} == Kx=EDH ]] ); then
                         kx[nr_ciphers_found]+=" $(read_dhbits_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" quiet)"
                    fi
                    enc[nr_ciphers_found]="${TLS_CIPHER_ENC[i]}"
                    export2[nr_ciphers_found]="${TLS_CIPHER_EXPORT[i]}"
                    sigalg[nr_ciphers_found]=""
                    "$SHOW_SIGALGO" && [[ -r "$HOSTCERT" ]] && \
                         sigalg[nr_ciphers_found]="$(read_sigalg_from_file "$HOSTCERT")"
                    nr_ciphers_found+=1
               fi
               if [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]] && [[ $TLS_NR_CIPHERS -ne 0 ]]; then
                    cipher="$(rfc2openssl "$cipher")"
                    # If there is no OpenSSL name for the cipher, then use the RFC name
                    [[ -z "$cipher" ]] && cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")
               fi
               order+="$cipher "
          done
     elif [[ -n "$order" ]] && [[ "$DISPLAY_CIPHERNAMES" =~ rfc ]]; then
          rfc_order=""
          while read -d " " cipher; do
               rfc_cipher="$(openssl2rfc "$cipher")"
               if [[ -n "$rfc_cipher" ]]; then
                    rfc_order+="$rfc_cipher "
               else
                    rfc_order+="$cipher "
               fi
          done <<< "$order"
          order="$rfc_order"
     fi

     if [[ -n "$order" ]]; then
          add_proto_offered "$proto" yes
          if "$wide"; then
               for (( i=0 ; i<nr_ciphers_found; i++ )); do
                    neat_list "${normalized_hexcode[i]}" "${ciph[i]}" "${kx[i]}" "${enc[i]}" "${export2[i]}" "true"
                    outln "${sigalg[i]}"
                    id="cipher-${proto}_${normalized_hexcode[i]}"
                    fileout "$id" "INFO" "$proto_text  $(neat_list "${normalized_hexcode[i]}" "${ciph[i]}" "${kx[i]}" "${enc[i]}" "${export2[i]}")"
               done
          else
               outln
               out "$(printf "    %-10s " "$proto_text: ")"
               if [[ "$COLOR" -le 2 ]]; then
                    out "$(out_row_aligned_max_width "$order" "               " $TERM_WIDTH)"
               else
                    out_row_aligned_max_width_by_entry "$order" "               " $TERM_WIDTH pr_cipher_quality
               fi
          fi
          fileout "cipherorder_${proto_text//./_}" "INFO" "$order"
     else
          # Order doesn't contain any ciphers, so we can safely unset the protocol and put a dash out
          add_proto_offered "$proto" no
          outln " -"
     fi

     tmpfile_handle ${FUNCNAME[0]}-$proto.txt
     return 0
}


# arg1 is OpenSSL s_client parameter or empty
#
get_host_cert() {
     local tmpvar=$TEMPDIR/${FUNCNAME[0]}.txt     # change later to $TMPFILE

     $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI $1") 2>/dev/null </dev/null >$tmpvar
     if sclient_connect_successful $? $tmpvar; then
          awk '/-----BEGIN/,/-----END/ { print $0 }' $tmpvar >$HOSTCERT
          return 0
     else
          if [[ -z "$1" ]]; then
               prln_warning "could not retrieve host certificate!"
               fileout "host_certificate_Problem" "WARN" "Could not retrieve host certificate!"
          fi
          return 1
     fi
     #tmpfile_handle ${FUNCNAME[0]}.txt
     #return $((${PIPESTATUS[0]} + ${PIPESTATUS[1]}))
}

verify_retcode_helper() {
     local ret=0
     local -i retcode=$1

     case $retcode in
          # codes from ./doc/apps/verify.pod | verify(1ssl)
          44) tm_out "(different CRL scope)" ;;                    # X509_V_ERR_DIFFERENT_CRL_SCOPE
          26) tm_out "(unsupported certificate purpose)" ;;        # X509_V_ERR_INVALID_PURPOSE
          24) tm_out "(certificate unreadable)" ;;                 # X509_V_ERR_INVALID_CA
          23) tm_out "(certificate revoked)" ;;                    # X509_V_ERR_CERT_REVOKED
          21) tm_out "(chain incomplete, only 1 cert provided)" ;; # X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE
          20) tm_out "(chain incomplete)" ;;                       # X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY
          19) tm_out "(self signed CA in chain)" ;;                # X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN
          18) tm_out "(self signed)" ;;                            # X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT
          10) tm_out "(expired)" ;;                                # X509_V_ERR_CERT_HAS_EXPIRED
          9)  tm_out "(not yet valid)" ;;                          # X509_V_ERR_CERT_NOT_YET_VALID
          2)  tm_out "(issuer cert missing)" ;;                    # X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT
          *) ret=1 ; tm_out " (unknown, pls report) $1" ;;
     esac
     return $ret
}

# arg1: number of certificate if provided >1
determine_trust() {
     local jsonID="$1"
     local json_postfix="$2"
     local -i i=1
     local -i num_ca_bundles=0
     local bundle_fname=""
     local -a certificate_file verify_retcode trust
     local ok_was=""
     local notok_was=""
     local all_ok=true
     local some_ok=false
     local code
     local ca_bundles=""
     local spaces="                              "
     local -i certificates_provided=1+$(grep -c "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TEMPDIR/intermediatecerts.pem)
     local addtl_warning

     # If $json_postfix is not empty, then there is more than one certificate
     # and the output should should be indented by two more spaces.
     [[ -n $json_postfix ]] && spaces="                                "

     case $OSSL_VER_MAJOR.$OSSL_VER_MINOR in
          1.0.2|1.1.0|1.1.1|2.[1-9].*|3.*)           # 2.x is LibreSSL. 2.1.1 was tested to work, below is not sure
               :
          ;;
          *)   addtl_warning="Your $OPENSSL <= 1.0.2 might be too unreliable to determine trust"
               fileout "${jsonID}${json_postfix}" "WARN" "$addtl_warning"
               addtl_warning="(${addtl_warning})"
          ;;
     esac
     debugme tmln_out

     # if you run testssl.sh from a different path /you can set either TESTSSL_INSTALL_DIR or CA_BUNDLES_PATH to find the CA BUNDLES
     if [[ -z "$CA_BUNDLES_PATH" ]]; then
          ca_bundles="$TESTSSL_INSTALL_DIR/etc/*.pem"
     else
          ca_bundles="$CA_BUNDLES_PATH/*.pem"
     fi
     for bundle_fname in $ca_bundles; do
          certificate_file[i]=$(basename ${bundle_fname//.pem})
          if [[ ! -r $bundle_fname ]]; then
               prln_warning "\"$bundle_fname\" cannot be found / not readable"
               return 1
          fi
          debugme printf -- " %-12s" "${certificate_file[i]}"
          # Set SSL_CERT_DIR to /dev/null so that $OPENSSL verify will only use certificates in $bundle_fname
          # in a subshell because that should be valid here only
          (export SSL_CERT_DIR="/dev/null"; export SSL_CERT_FILE="/dev/null"
          if [[ $certificates_provided -ge 2 ]]; then
               $OPENSSL verify -purpose sslserver -CAfile <(cat $ADDTL_CA_FILES "$bundle_fname") -untrusted $TEMPDIR/intermediatecerts.pem $HOSTCERT >$TEMPDIR/${certificate_file[i]}.1 2>$TEMPDIR/${certificate_file[i]}.2
          else
               $OPENSSL verify -purpose sslserver -CAfile <(cat $ADDTL_CA_FILES "$bundle_fname") $HOSTCERT >$TEMPDIR/${certificate_file[i]}.1 2>$TEMPDIR/${certificate_file[i]}.2
          fi)
          verify_retcode[i]=$(awk '/error [1-9][0-9]? at [0-9]+ depth lookup:/ { if (!found) {print $2; found=1} }' $TEMPDIR/${certificate_file[i]}.1 $TEMPDIR/${certificate_file[i]}.2)
          [[ -z "${verify_retcode[i]}" ]] && verify_retcode[i]=0
          if [[ ${verify_retcode[i]} -eq 0 ]]; then
               trust[i]=true
               some_ok=true
               [[ -z "$GOOD_CA_BUNDLE" ]] && GOOD_CA_BUNDLE="$bundle_fname"
               debugme tm_svrty_good "Ok   "
               debugme tmln_out "${verify_retcode[i]}"
          else
               trust[i]=false
               all_ok=false
               debugme tm_svrty_high "not trusted "
               debugme tmln_out "${verify_retcode[i]}"
          fi
          ((i++))
     done
     num_ca_bundles=$((i - 1))
     debugme tm_out " "
     if "$all_ok"; then
          # all stores ok
          pr_svrty_good "Ok   "; pr_warning "$addtl_warning"
          # we did to stdout the warning above already, so we could stay here with OK:
          fileout "${jsonID}${json_postfix}" "OK" "passed. $addtl_warning"
     else
          # at least one failed
          pr_svrty_critical "NOT ok"
          if ! "$some_ok"; then
               # all failed (we assume with the same issue), we're displaying the reason
               out " "
               code="$(verify_retcode_helper "${verify_retcode[1]}")"
               if [[ "$code" =~ "pls report" ]]; then
                    pr_warning "$code"
               else
                    out "$code"
               fi
               fileout "${jsonID}${json_postfix}" "CRITICAL" "failed $code. $addtl_warning"
               set_grade_cap "T" "Issues with certificate $code"
          else
               # is one ok and the others not ==> display the culprit store
               if "$some_ok"; then
                    pr_svrty_critical ":"
                    for ((i=1;i<=num_ca_bundles;i++)); do
                         if ${trust[i]}; then
                              ok_was="${certificate_file[i]} $ok_was"
                         else
                              #code="$(verify_retcode_helper ${verify_retcode[i]})"
                              #notok_was="${certificate_file[i]} $notok_was"
                              pr_svrty_high " ${certificate_file[i]} "
                              code="$(verify_retcode_helper "${verify_retcode[i]}")"
                              if [[ "$code" =~ "pls report" ]]; then
                                   pr_warning "$code"
                              else
                                   out "$code"
                              fi
                              notok_was="${certificate_file[i]} $code $notok_was"
                              set_grade_cap "T" "Issues with certificate $code"
                         fi
                    done
                    #pr_svrty_high "$notok_was "
                    #outln "$code"
                    outln
                    # lf + green ones
                    [[ "$DEBUG" -eq 0 ]] && tm_out "$spaces"
                    pr_svrty_good "OK: $ok_was"
               fi
               fileout "${jsonID}${json_postfix}" "CRITICAL" "Some certificate trust checks failed -> $notok_was $addtl_warning, OK -> $ok_was"
          fi
          [[ -n "$addtl_warning" ]] && out "\n$spaces" && pr_warning "$addtl_warning"
     fi
     outln
     return 0
}

# not handled: Root CA supplied ("contains anchor" in SSLlabs terminology)

tls_time() {
     local difftime
     local spaces="               "
     local jsonID="TLS_timestamp"

     pr_bold " TLS clock skew" ; out "$spaces"

     if ( [[ "$STARTTLS_PROTOCOL" =~ ldap ]] || [[ "$STARTTLS_PROTOCOL" =~ irc ]] ); then
          prln_local_problem "STARTTLS/$STARTTLS_PROTOCOL and --ssl-native collide here"
          return 1
     fi

     TLS_DIFFTIME_SET=true                                       # this is a switch whether we want to measure the remote TLS_TIME
     tls_sockets "01" "$TLS_CIPHER"                              # try first TLS 1.0 (most frequently used protocol)
     [[ -z "$TLS_TIME" ]] && tls_sockets "03" "$TLS12_CIPHER"    #           TLS 1.2
     [[ -z "$TLS_TIME" ]] && tls_sockets "02" "$TLS_CIPHER"      #           TLS 1.1
     [[ -z "$TLS_TIME" ]] && tls_sockets "00" "$TLS_CIPHER"      #           SSL 3

     if [[ -n "$TLS_TIME" ]]; then                               # nothing returned a time!
          difftime=$((TLS_TIME -  TLS_NOW))                      # TLS_NOW has been set in tls_sockets()
          if [[ "${#difftime}" -gt 5 ]]; then
               # openssl >= 1.0.1f fills this field with random values! --> good for possible fingerprint
               out "Random values, no fingerprinting possible "
               fileout "$jsonID" "INFO" "random"
          else
               [[ $difftime != "-"* ]] && [[ $difftime != "0" ]] && difftime="+$difftime"
               out "$difftime"; out " sec from localtime";
               fileout "$jsonID" "INFO" "off by $difftime seconds from your localtime"
          fi
          debugme tm_out "$TLS_TIME"
          outln
     else
          outln "SSLv3 through TLS 1.2 didn't return a timestamp"
          fileout "$jsonID" "INFO" "None returned by SSLv3 through TLSv1.2"
     fi
     TLS_DIFFTIME_SET=false                                      # reset the switch to save calls to date and friend in tls_sockets()
     return 0
}

# core function determining whether handshake succeeded or not
# arg1: return value of "openssl s_client connect"
# arg2: temporary file with the server hello
# returns 0 if connect was successful, 1 if not
#
sclient_connect_successful() {
     local server_hello="$(cat -v "$2")"
     local re='Master-Key: ([^\
]*)'

     [[ $1 -eq 0 ]] && return 0
     if [[ "$server_hello" =~ $re ]]; then
          [[ -n "${BASH_REMATCH[1]}" ]] && return 0
     fi
     [[ "$server_hello" =~ (New|Reused)", "(SSLv[23]|TLSv1(\.[0-3])?(\/SSLv3)?)", Cipher is "([A-Z0-9]+-[A-Za-z0-9\-]+|TLS_[A-Za-z0-9_]+) ]] && return 0
     # what's left now is: master key empty and Session-ID not empty
     # ==> probably client-based auth with x509 certificate. We handle that at other places
     #
     # For robustness we also detected here network / server connectivity problems:
     # Just need to check whether $TMPFILE=$2 is empty
     if [[ ! -s "$2" ]]; then
          ((NR_OSSL_FAIL++))
          connectivity_problem $NR_OSSL_FAIL $MAX_OSSL_FAIL "openssl s_client connect problem" "repeated openssl s_client connect problem, doesn't make sense to continue"
     fi
     return 1
}

extract_new_tls_extensions() {
     local tls_extensions

     # this is not beautiful (grep+sed)
     # but maybe we should just get the ids and do a private matching, according to
     # https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
     tls_extensions=$(grep -a 'TLS server extension ' "$1" | \
          sed -e 's/TLS server extension //g' -e 's/\" (id=/\/#/g' \
              -e 's/,.*$/,/g' -e 's/),$/\"/g' \
              -e 's/elliptic curves\/#10/supported_groups\/#10/g')
     tls_extensions=$(echo $tls_extensions)       # into one line

     if [[ -n "$tls_extensions" ]]; then
          # check to see if any new TLS extensions were returned and add any new ones to TLS_EXTENSIONS
          while read -d "\"" -r line; do
               if [[ $line != "" ]] && [[ ! "$TLS_EXTENSIONS" =~ "$line" ]]; then
#FIXME: This is a string of quoted strings, so this seems to determine the output format already. Better e.g. would be an array
                    TLS_EXTENSIONS+=" \"${line}\""
               fi
          done <<<$tls_extensions
          [[ "${TLS_EXTENSIONS:0:1}" == " " ]] && TLS_EXTENSIONS="${TLS_EXTENSIONS:1}"
     fi
}

# Note that since, at the moment, this function is only called by run_server_defaults()
# and run_heartbleed(), this function does not look for the status request or NPN
# extensions. For run_heartbleed(), only the heartbeat extension needs to be detected.
# For run_server_defaults(), the status request and NPN would already be detected by
# get_server_certificate(), if they are supported. In the case of the status extension,
# since including a status request extension in a ClientHello does not work for GOST
# only servers. In the case of NPN, since a server will not include both the NPN and
# ALPN extensions in the same ServerHello.
#
determine_tls_extensions() {
     local addcmd
     local -i success=1
     local line params="" tls_extensions=""
     local alpn_proto alpn="" alpn_list_len_hex alpn_extn_len_hex
     local -i alpn_list_len alpn_extn_len
     local cbc_cipher_list="ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DH-RSA-AES256-SHA256:DH-DSS-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DH-RSA-AES256-SHA:DH-DSS-AES256-SHA:ECDHE-RSA-CAMELLIA256-SHA384:ECDHE-ECDSA-CAMELLIA256-SHA384:DHE-RSA-CAMELLIA256-SHA256:DHE-DSS-CAMELLIA256-SHA256:DH-RSA-CAMELLIA256-SHA256:DH-DSS-CAMELLIA256-SHA256:DHE-RSA-CAMELLIA256-SHA:DHE-DSS-CAMELLIA256-SHA:DH-RSA-CAMELLIA256-SHA:DH-DSS-CAMELLIA256-SHA:ECDH-RSA-AES256-SHA384:ECDH-ECDSA-AES256-SHA384:ECDH-RSA-AES256-SHA:ECDH-ECDSA-AES256-SHA:ECDH-RSA-CAMELLIA256-SHA384:ECDH-ECDSA-CAMELLIA256-SHA384:AES256-SHA256:AES256-SHA:CAMELLIA256-SHA256:CAMELLIA256-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DH-RSA-AES128-SHA256:DH-DSS-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:DH-RSA-AES128-SHA:DH-DSS-AES128-SHA:ECDHE-RSA-CAMELLIA128-SHA256:ECDHE-ECDSA-CAMELLIA128-SHA256:DHE-RSA-CAMELLIA128-SHA256:DHE-DSS-CAMELLIA128-SHA256:DH-RSA-CAMELLIA128-SHA256:DH-DSS-CAMELLIA128-SHA256:DHE-RSA-SEED-SHA:DHE-DSS-SEED-SHA:DH-RSA-SEED-SHA:DH-DSS-SEED-SHA:DHE-RSA-CAMELLIA128-SHA:DHE-DSS-CAMELLIA128-SHA:DH-RSA-CAMELLIA128-SHA:DH-DSS-CAMELLIA128-SHA:ECDH-RSA-AES128-SHA256:ECDH-ECDSA-AES128-SHA256:ECDH-RSA-AES128-SHA:ECDH-ECDSA-AES128-SHA:ECDH-RSA-CAMELLIA128-SHA256:ECDH-ECDSA-CAMELLIA128-SHA256:AES128-SHA256:AES128-SHA:CAMELLIA128-SHA256:SEED-SHA:CAMELLIA128-SHA:IDEA-CBC-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DH-RSA-DES-CBC3-SHA:DH-DSS-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:EXP1024-DHE-DSS-DES-CBC-SHA:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DH-RSA-DES-CBC-SHA:DH-DSS-DES-CBC-SHA:EXP1024-DES-CBC-SHA:DES-CBC-SHA:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-DH-DSS-DES-CBC-SHA:EXP-DH-RSA-DES-CBC-SHA"
     local cbc_cipher_list_hex="c0,28, c0,24, c0,14, c0,0a, 00,6b, 00,6a, 00,69, 00,68, 00,39, 00,38, 00,37, 00,36, c0,77, c0,73, 00,c4, 00,c3, 00,c2, 00,c1, 00,88, 00,87, 00,86, 00,85, c0,2a, c0,26, c0,0f, c0,05, c0,79, c0,75, 00,3d, 00,35, 00,c0, 00,84, c0,3d, c0,3f, c0,41, c0,43, c0,45, c0,49, c0,4b, c0,4d, c0,4f, c0,27, c0,23, c0,13, c0,09, 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,29, c0,25, c0,0e, c0,04, c0,78, c0,74, 00,3c, 00,2f, 00,ba, 00,96, 00,41, 00,07, c0,3c, c0,3e, c0,40, c0,42, c0,44, c0,48, c0,4a, c0,4c, c0,4e, c0,12, c0,08, 00,16, 00,13, 00,10, 00,0d, c0,0d, c0,03, 00,0a, fe,ff, ff,e0, 00,63, 00,15, 00,12, 00,0f, 00,0c, 00,62, 00,09, fe,fe, ff,e1, 00,14, 00,11, 00,08, 00,06, 00,0b, 00,0e"
     local using_sockets=true

     [[ "$OPTIMAL_PROTO" == -ssl2 ]] && return 0
     "$SSL_NATIVE" && using_sockets=false

     if "$using_sockets"; then
          tls_extensions="00,01,00,01,02, 00,02,00,00, 00,04,00,00, 00,12,00,00, 00,16,00,00, 00,17,00,00"
          if [[ -z $STARTTLS ]]; then
               for alpn_proto in $ALPN_PROTOs; do
                    alpn+=",$(printf "%02x" ${#alpn_proto}),$(string_to_asciihex "$alpn_proto")"
               done
               alpn_list_len=${#alpn}/3
               alpn_list_len_hex=$(printf "%04x" $alpn_list_len)
               alpn_extn_len=$alpn_list_len+2
               alpn_extn_len_hex=$(printf "%04x" $alpn_extn_len)
               tls_extensions+=", 00,10,${alpn_extn_len_hex:0:2},${alpn_extn_len_hex:2:2},${alpn_list_len_hex:0:2},${alpn_list_len_hex:2:2}$alpn"
          fi
          if [[ ! "$TLS_EXTENSIONS" =~ encrypt-then-mac ]]; then
               tls_sockets "03" "$cbc_cipher_list_hex, 00,ff" "all" "$tls_extensions"
               success=$?
          fi
          if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then
               tls_sockets "03" "$TLS12_CIPHER" "all" "$tls_extensions"
               success=$?
          fi
          [[ $success -eq 2 ]] && success=0
          [[ $success -eq 0 ]] && extract_new_tls_extensions "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt"
          if [[ -r "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" ]]; then
               cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" $TMPFILE
               tmpfile_handle ${FUNCNAME[0]}.txt
          fi
     else
          if "$HAS_ALPN" && [[ -z $STARTTLS ]]; then
               params="-alpn \"${ALPN_PROTOs// /,}\""  # we need to replace " " by ","
          elif "$HAS_NPN" && [[ -z $STARTTLS ]]; then
               params="-nextprotoneg \"$NPN_PROTOs\""
          fi
          if [[ -z "$OPTIMAL_PROTO" ]] && [[ -z "$SNI" ]] && "$HAS_NO_SSL2"; then
               addcmd="-no_ssl2"
          else
               addcmd="$SNI"
          fi
          if [[ ! "$TLS_EXTENSIONS" =~ encrypt-then-mac ]]; then
               $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $addcmd $OPTIMAL_PROTO -tlsextdebug $params -cipher $cbc_cipher_list") </dev/null 2>$ERRFILE >$TMPFILE
               sclient_connect_successful $? $TMPFILE
               success=$?
          fi
          if [[ $success -ne 0 ]]; then
               $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $addcmd $OPTIMAL_PROTO -tlsextdebug $params") </dev/null 2>$ERRFILE >$TMPFILE
               sclient_connect_successful $? $TMPFILE
               success=$?
          fi
          [[ $success -eq 0 ]] && extract_new_tls_extensions $TMPFILE
          tmpfile_handle ${FUNCNAME[0]}.txt
     fi
     return $success
}

extract_certificates() {
     local version="$1"
     local savedir
     local -i i success nrsaved=0
     local issuerDN CAsubjectDN previssuerDN

     # Place the server's certificate in $HOSTCERT and any intermediate
     # certificates that were provided in $TEMPDIR/intermediatecerts.pem
     savedir="$PWD"; cd $TEMPDIR
     # https://backreference.org/2010/05/09/ocsp-verification-with-openssl/
     if [[ "$version" == ssl2 ]]; then
          awk -v n=-1 '/Server certificate/ {start=1}
               /-----BEGIN CERTIFICATE-----/{ if (start) {inc=1; n++} }
               inc { print > ("level" n ".crt") }
               /---END CERTIFICATE-----/{ inc=0 }' $TMPFILE
     else
          awk -v n=-1 '/Certificate chain/ {start=1}
               /-----BEGIN CERTIFICATE-----/{ if (start) {inc=1; n++} }
               inc { print > ("level" n ".crt") }
               /---END CERTIFICATE-----/{ inc=0 }' $TMPFILE
     fi
     [[ -s level0.crt ]] && nrsaved=$(count_words "$(echo level?.crt 2>/dev/null)")
     if [[ $nrsaved -eq 0 ]]; then
          success=1
     else
          success=0
          CERTIFICATE_LIST_ORDERING_PROBLEM=false
          mv level0.crt $HOSTCERT
          if [[ $nrsaved -eq 1 ]]; then
               echo "" > $TEMPDIR/intermediatecerts.pem
          else
               cat level?.crt > $TEMPDIR/intermediatecerts.pem
               issuerDN="$($OPENSSL x509 -in $HOSTCERT -noout -issuer 2>/dev/null)"
               issuerDN="${issuerDN:8}"
               previssuerDN="$issuerDN"
               # The second certificate (level1.crt) SHOULD be issued to the CA
               # that issued the server's certificate. But, according to RFC 8446
               # clients SHOULD be prepared to handle cases in which the server
               # does not order the certificates correctly.
               for (( i=1; i < nrsaved; i++ )); do
                    CAsubjectDN="$($OPENSSL x509 -in "level$i.crt" -noout -subject  2>/dev/null)"
                    if [[ "${CAsubjectDN:9}" == "$issuerDN" ]]; then
                         cp "level$i.crt" $TEMPDIR/hostcert_issuer.pem
                         issuerDN="" # set to empty to prevent further matches
                    fi
                    [[ "${CAsubjectDN:9}" != "$previssuerDN" ]] && CERTIFICATE_LIST_ORDERING_PROBLEM=true
                    "$CERTIFICATE_LIST_ORDERING_PROBLEM" && [[ -z "$issuerDN" ]] && break
                    previssuerDN="$($OPENSSL x509 -in "level$i.crt" -noout -issuer  2>/dev/null)"
                    previssuerDN="${previssuerDN:8}"
               done
               # This should never happen, but if more than one certificate was
               # provided and none of them belong to the CA that issued the
               # server's certificate, then the extra certificates should just
               # be deleted. There is code elsewhere that assumes that if
               # $TEMPDIR/intermediatecerts.pem is non-empty, then
               # $TEMPDIR/hostcert_issuer.pem is also present.
               [[ -n "$issuerDN" ]] && echo "" > $TEMPDIR/intermediatecerts.pem
               rm level?.crt
          fi
     fi
     cd "$savedir"
     return $success
}

extract_stapled_ocsp() {
     local response="$(cat $TMPFILE)"
     local ocsp tmp
     local -i ocsp_len

     STAPLED_OCSP_RESPONSE=""
     if [[ "$response" =~ CertificateStatus ]]; then
          # This is OpenSSL 1.1.0 or 1.1.1 and the response
          # is TLS 1.2 or earlier.
          ocsp="${response##*CertificateStatus}"
          ocsp="16${ocsp#*16}"
          ocsp="${ocsp%%<<<*}"
          ocsp="$(strip_spaces "$(newline_to_spaces "$ocsp")")"
          ocsp="${ocsp:8}"
     elif [[ "$response" =~ "TLS server extension \"status request\" (id=5), len=0" ]]; then
          # This is not OpenSSL 1.1.0 or 1.1.1, and the response
          # is TLS 1.2 or earlier.
          ocsp="${response%%OCSP response:*}"
          ocsp="${ocsp##*<<<}"
          ocsp="16${ocsp#*16}"
          ocsp="$(strip_spaces "$(newline_to_spaces "$ocsp")")"
          ocsp="${ocsp:8}"
     elif [[ "$response" =~ "TLS server extension \"status request\" (id=5), len=" ]]; then
          # This is OpenSSL 1.1.1 and the response is TLS 1.3.
          ocsp="${response##*TLS server extension \"status request\" (id=5), len=}"
          ocsp="${ocsp%%<<<*}"
          tmp="${ocsp%%[!0-9]*}"
          ocsp="${ocsp#$tmp}"
          ocsp_len=2*$tmp
          ocsp="$(awk ' { print $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15 $16 $17 } ' <<< "$ocsp" | sed 's/-//')"
          ocsp="$(strip_spaces "$(newline_to_spaces "$ocsp")")"
          ocsp="${ocsp:0:ocsp_len}"
     else
          return 0
     fi
     # Determine whether this is a single OCSP response or a sequence of
     # responses and then extract just the response for the server's
     # certificate.
     if [[ "${ocsp:0:2}" == "01" ]]; then
          STAPLED_OCSP_RESPONSE="${ocsp:8}"
     elif [[ "${ocsp:0:2}" == "02" ]]; then
          ocsp_len=2*$(hex2dec "${tls_certificate_status_ascii:8:6}")
          STAPLED_OCSP_RESPONSE="${ocsp:14:ocsp_len}"
     fi
     return 0
}

# arg1 is "<OpenSSL cipher>"
# arg2 is a list of protocols to try (tls1_2, tls1_1, tls1, ssl3) or empty (if all should be tried)
get_server_certificate() {
     local protocols_to_try proto
     local success ret
     local npn_params="" line
     local ciphers_to_test=""
     # Cipher suites that use a certifiate with an RSA (signature) public key
     local -r a_rsa="cc,13, cc,15, c0,30, c0,28, c0,14, 00,9f, cc,a8, cc,aa, c0,a3, c0,9f, 00,6b, 00,39, c0,77, 00,c4, 00,88, c0,45, c0,4d, c0,53, c0,61, c0,7d, c0,8b, 16,b7, 16,b9, c0,2f, c0,27, c0,13, 00,9e, c0,a2, c0,9e, 00,67, 00,33, c0,76, 00,be, 00,9a, 00,45, c0,44, c0,4c, c0,52, c0,60, c0,7c, c0,8a, c0,11, c0,12, 00,16, 00,15, 00,14, c0,10"
     # Cipher suites that use a certifiate with an RSA (encryption) public key
     local -r e_rsa="00,b7, c0,99, 00,ad, cc,ae, 00,9d, c0,a1, c0,9d, 00,3d, 00,35, 00,c0, 00,84, 00,95, c0,3d, c0,51, c0,69, c0,6f, c0,7b, c0,93, ff,01, 00,ac, c0,a0, c0,9c, 00,9c, 00,3c, 00,2f, 00,ba, 00,b6, 00,96, 00,41, c0,98, 00,07, 00,94, c0,3c, c0,50, c0,68, c0,6e, c0,7a, c0,92, 00,05, 00,04, 00,92, 00,0a, 00,93, fe,ff, ff,e0, 00,62, 00,09, 00,61, fe,fe, ff,e1, 00,64, 00,60, 00,08, 00,06, 00,03, 00,b9, 00,b8, 00,2e, 00,3b, 00,02, 00,01, ff,00"
     # Cipher suites that use a certifiate with a DSA public key
     local -r a_dss="00,a3, 00,6a, 00,38, 00,c3, 00,87, c0,43, c0,57, c0,81, 00,a2, 00,40, 00,32, 00,bd, 00,99, 00,44, c0,42, c0,56, c0,80, 00,66, 00,13, 00,63, 00,12, 00,65, 00,11"
     # Cipher suites that use a certifiate with a DH public key
     local -r a_dh="00,a5, 00,a1, 00,69, 00,68, 00,37, 00,36, 00,c2, 00,c1, 00,86, 00,85, c0,3f, c0,41, c0,55, c0,59, c0,7f, c0,83, 00,a4, 00,a0, 00,3f, 00,3e, 00,31, 00,30, 00,bc, 00,bb, 00,98, 00,97, 00,43, 00,42, c0,3e, c0,40, c0,54, c0,58, c0,7e, c0,82, 00,10, 00,0d, 00,0f, 00,0c, 00,0b, 00,0e"
     # Cipher suites that use a certifiate with an ECDH public key
     local -r a_ecdh="c0,32, c0,2e, c0,2a, c0,26, c0,0f, c0,05, c0,79, c0,75, c0,4b, c0,4f, c0,5f, c0,63, c0,89, c0,8d, c0,31, c0,2d, c0,29, c0,25, c0,0e, c0,04, c0,78, c0,74, c0,4a, c0,4e, c0,5e, c0,62, c0,88, c0,8c, c0,0c, c0,02, c0,0d, c0,03, c0,0b, c0,01"
     # Cipher suites that use a certifiate with an ECDSA public key
     local -r a_ecdsa="cc,14, c0,2c, c0,24, c0,0a, cc,a9, c0,af, c0,ad, c0,73, c0,49, c0,5d, c0,87, 16,b8, 16,ba, c0,2b, c0,23, c0,09, c0,ae, c0,ac, c0,72, c0,48, c0,5c, c0,86, c0,07, c0,08, c0,06"
     # Cipher suites that use a certifiate with a GOST public key
     local -r a_gost="00,80, 00,81, 00,82, 00,83"
     local using_sockets=true

     "$SSL_NATIVE" && using_sockets=false

     CERTIFICATE_LIST_ORDERING_PROBLEM=false
     if [[ "$1" =~ "tls1_3" ]]; then
          [[ $(has_server_protocol "tls1_3") -eq 1 ]] && return 1
          if "$HAS_TLS13" && "$HAS_SIGALGS"; then
               if [[ "$1" =~ "tls1_3_RSA" ]]; then
                    $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -showcerts -connect $NODEIP:$PORT $PROXY $SNI -tls1_3 -tlsextdebug -status -msg -sigalgs PSS+SHA256:PSS+SHA384") </dev/null 2>$ERRFILE >$TMPFILE
               elif [[ "$1" =~ "tls1_3_ECDSA" ]]; then
                    $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -showcerts -connect $NODEIP:$PORT $PROXY $SNI -tls1_3 -tlsextdebug -status -msg -sigalgs ECDSA+SHA256:ECDSA+SHA384") </dev/null 2>$ERRFILE >$TMPFILE
               else
                    return 1
               fi
               sclient_connect_successful $? $TMPFILE || return 1
               DETECTED_TLS_VERSION="0304"
               extract_certificates "tls1_3"
               extract_stapled_ocsp
               success=$?
          else
               # For STARTTLS protcols not being implemented yet via sockets this is a bypass otherwise it won't be usable at all (e.g. LDAP)
               if ( [[ "$STARTTLS" =~ ldap ]] || [[ "$STARTTLS" =~ irc ]] ); then
                    return 1
               elif [[ "$1" =~ "tls1_3_RSA" ]]; then
                    tls_sockets "04" "$TLS13_CIPHER" "all" "00,12,00,00, 00,05,00,05,01,00,00,00,00, 00,0d,00,10,00,0e,08,04,08,05,08,06,04,01,05,01,06,01,02,01"
               elif [[ "$1" =~ "tls1_3_ECDSA" ]]; then
                    tls_sockets "04" "$TLS13_CIPHER" "all" "00,12,00,00, 00,05,00,05,01,00,00,00,00, 00,0d,00,0a,00,08,04,03,05,03,06,03,02,03"
               else
                    return 1
               fi
               success=$?
               [[ $success -eq 0 ]] || return 1
               cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" $TMPFILE
          fi
          [[ $success -eq 0 ]] && add_proto_offered tls1_3 yes
          extract_new_tls_extensions $TMPFILE
          tmpfile_handle ${FUNCNAME[0]}.txt
          return $success
     fi

     "$HAS_NPN" && [[ -z "$STARTTLS" ]] && npn_params="-nextprotoneg \"$NPN_PROTOs\""

     if [[ -n "$2" ]]; then
          protocols_to_try="$2"
     else
          protocols_to_try="tls1_2 tls1_1 tls1 ssl3"
     fi

     # throwing 1st every cipher/protocol at the server to know what works
     success=7

     if [[ "$OPTIMAL_PROTO" == -ssl2 ]]; then
          success=1
          sslv2_sockets "" "true"
          if [[ $? -eq 3 ]]; then
               mv $TEMPDIR/$NODEIP.parse_sslv2_serverhello.txt $TMPFILE
               success=0
          fi
          tmpfile_handle ${FUNCNAME[0]}.txt
          return $success
     fi

     if "$using_sockets"; then
          protocols_to_try="${protocols_to_try/tls1_2/03}"
          protocols_to_try="${protocols_to_try/tls1_1/02}"
          protocols_to_try="${protocols_to_try/tls1/01}"
          protocols_to_try="${protocols_to_try/ssl3/00}"

          [[ "$1" =~ aRSA ]] && ciphers_to_test+=", $a_rsa"
          [[ "$1" =~ eRSA ]] && ciphers_to_test+=", $e_rsa"
          [[ "$1" =~ aDSS ]] && ciphers_to_test+=", $a_dss"
          [[ "$1" =~ aDH ]] && ciphers_to_test+=", $a_dh"
          [[ "$1" =~ aECDH ]] && ciphers_to_test+=", $a_ecdh"
          [[ "$1" =~ aECDSA ]] && ciphers_to_test+=", $a_ecdsa"
          [[ "$1" =~ aGOST ]] && ciphers_to_test+=", $a_gost"

          [[ -z "$ciphers_to_test" ]] && return 1
          ciphers_to_test="${ciphers_to_test:2}"

          for proto in $protocols_to_try; do
               [[ 1 -eq $(has_server_protocol $proto) ]] && continue
               tls_sockets "$proto" "$ciphers_to_test, 00,ff" "all" "00,12,00,00, 00,05,00,05,01,00,00,00,00"
               ret=$?
               [[ $ret -eq 0 ]] && success=0 && break
               [[ $ret -eq 2 ]] && success=0 && break
          done                          # this loop is needed for IIS6 and others which have a handshake size limitations
          if [[ $success -eq 7 ]]; then
               # "-status" above doesn't work for GOST only servers, so we do another test without it and see whether that works then:
               tls_sockets "$proto" "$ciphers_to_test, 00,ff" "all" "00,12,00,00"
               ret=$?
               [[ $ret -eq 0 ]] && success=0
               [[ $ret -eq 2 ]] && success=0
               if [[ $success -eq 7 ]]; then
                    if [ -z "$1" ]; then
                         prln_warning "Strange, no SSL/TLS protocol seems to be supported (error around line $((LINENO - 6)))"
                    fi
                    tmpfile_handle ${FUNCNAME[0]}.txt
                    return 7  # this is ugly, I know
               else
                    GOST_STATUS_PROBLEM=true
               fi
          fi
          cp $TEMPDIR/$NODEIP.parse_tls_serverhello.txt $TMPFILE

          # When "$2" is empty, get_server_certificate() is being called with SNI="".
          # In case the extensions returned by the server differ depending on wheter
          # SNI is provided or not, don't collect extensions when SNI="" (unless
          # no DNS name was provided at the command line).
          [[ -z "$2" ]] && extract_new_tls_extensions $TMPFILE
     else
          # no sockets, openssl
          ciphers_to_test="$1"
          if [[ "$1" =~ aRSA ]] && [[ "$1" =~ eRSA ]]; then
               ciphers_to_test="${ciphers_to_test/eRSA/}"
          elif [[ "$1" =~ aRSA ]]; then
               ciphers_to_test="${ciphers_to_test/aRSA/}"
               for ciph in $(colon_to_spaces $(actually_supported_osslciphers "aRSA")); do
                    [[ "$ciph" =~ -RSA- ]] && ciphers_to_test+=":$ciph"
               done
          elif [[ "$1" =~ eRSA ]]; then
               ciphers_to_test="${ciphers_to_test/eRSA/}"
               for ciph in $(colon_to_spaces $(actually_supported_osslciphers "aRSA")); do
                    [[ ! "$ciph" =~ -RSA- ]] && ciphers_to_test+=":$ciph"
               done
          fi
          ciphers_to_test="${ciphers_to_test/::/:}"
          [[ "${ciphers_to_test:0:1}" == : ]] &&  ciphers_to_test="${ciphers_to_test:1}"
          [[ $(count_ciphers $(actually_supported_osslciphers "$ciphers_to_test")) -ge 1 ]] || return 1

          for proto in $protocols_to_try; do
               [[ 1 -eq $(has_server_protocol $proto) ]] && continue
               [[ "$proto" == ssl3 ]] && ! "$HAS_SSL3" && continue
               addcmd=""
               $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -cipher $ciphers_to_test -showcerts -connect $NODEIP:$PORT $PROXY $SNI -$proto -tlsextdebug $npn_params -status -msg") </dev/null 2>$ERRFILE >$TMPFILE
               if sclient_connect_successful $? $TMPFILE; then
                    success=0
                    break               # now we have the certificate
               fi
          done                          # this loop is needed for IIS6 and others which have a handshake size limitations
          if [[ $success -eq 7 ]]; then
               # "-status" above doesn't work for GOST only servers, so we do another test without it and see whether that works then:
               [[ "$proto" == ssl3 ]] && ! "$HAS_SSL3" && return 7
               $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -cipher $ciphers_to_test -showcerts -connect $NODEIP:$PORT $PROXY $SNI -$proto -tlsextdebug") </dev/null 2>>$ERRFILE >$TMPFILE
               if ! sclient_connect_successful $? $TMPFILE; then
                    if [ -z "$1" ]; then
                         prln_warning "Strange, no SSL/TLS protocol seems to be supported (error around line $((LINENO - 6)))"
                    fi
                    tmpfile_handle ${FUNCNAME[0]}.txt
                    return 7  # this is ugly, I know
               else
                    GOST_STATUS_PROBLEM=true
               fi
          fi
          case "$proto" in
               "tls1_2") DETECTED_TLS_VERSION="0303" ;;
               "tls1_1") DETECTED_TLS_VERSION="0302" ;;
               "tls1") DETECTED_TLS_VERSION="0301" ;;
               "ssl3") DETECTED_TLS_VERSION="0300" ;;
          esac
          # When "$2" is empty, get_server_certificate() is being called with SNI="".
          # In case the extensions returned by the server differ depending on wheter
          # SNI is provided or not, don't collect extensions when SNI="" (unless
          # no DNS name was provided at the command line).
          [[ -z "$2" ]] && extract_new_tls_extensions $TMPFILE

          extract_certificates "$proto"
          extract_stapled_ocsp
          success=$?
     fi
     tmpfile_handle ${FUNCNAME[0]}.txt
     return $success
}

# arg1: path to certificate
# returns CN
get_cn_from_cert() {
     local subject

     # attention! openssl 1.0.2 doesn't properly handle online output from certificates from trustwave.com/github.com
     #FIXME: use -nameopt oid for robustness

     # for e.g. russian sites -esc_msb,utf8 works in an UTF8 terminal -- any way to check platform independent?
     # see x509(1ssl):
     subject="$($OPENSSL x509 -in $1 -noout -subject -nameopt multiline,-align,sname,-esc_msb,utf8,-space_eq 2>>$ERRFILE)"
     echo "$(awk -F'=' '/CN=/ { print $2 }' <<< "$subject" | tr '\n' ' ')"
     return $?
}

# Return 0 if the name provided in arg1 is a wildcard name
is_wildcard()
{
     local certname="$1"

     # If the first label in the DNS name begins "xn--", then assume it is an
     # A-label and not a wildcard name (RFC 6125, Section 6.4.3).
     [[ "${certname:0:4}" == "xn--" ]] && return 1

     # Remove part of name preceding '*' or '.'. If no "*" appears in the
     # left-most label, then it is not a wildcard name (RFC 6125, Section 6.4.3).
     basename="$(echo -n "$certname" | sed 's/^[_a-zA-Z0-9\-]*//')"
     [[ "${basename:0:1}" != "*" ]] && return 1 # not a wildcard name

     # Check that there are no additional wildcard ('*') characters or any
     # other characters that do not belong in a DNS name.
     [[ -n $(echo -n "${basename:1}" | sed 's/^[_\.a-zA-Z0-9\-]*//') ]] && return 1
     return 0
}

# Return 0 if the name provided in arg2 is a wildcard name and it matches the name provided in arg1.
wildcard_match()
{
     local servername="$1"
     local certname="$2"
     local basename
     local -i basename_offset len_certname len_part1 len_basename
     local -i len_servername len_wildcard

     len_servername=${#servername}
     len_certname=${#certname}

     # Use rules from RFC 6125 to perform the match.

     # Assume the "*" in the wildcard needs to be replaced by one or more
     # characters, although RFC 6125 is not clear about that.
     [[ $len_servername -lt $len_certname ]] && return 1

     is_wildcard "$certname"
     [[ $? -ne 0 ]] && return 1

     # Comparisons of DNS names are case insensitive, so convert both names to uppercase.
     certname="$(toupper "$certname")"
     servername="$(toupper "$servername")"

     # Extract part of name that comes after the "*"
     basename="$(echo -n "$certname" | sed 's/^[_A-Z0-9\-]*\*//')"
     len_basename=${#basename}
     len_part1=$len_certname-$len_basename-1
     len_wildcard=$len_servername-$len_certname+1
     basename_offset=$len_servername-$len_basename

     # Check that initial part of $servername matches initial part of $certname
     # and that final part of $servername matches final part of $certname.
     [[ "${servername:0:len_part1}" != "${certname:0:len_part1}" ]] && return 1
     [[ "${servername:basename_offset:len_basename}" != "$basename" ]] && return 1

     # Check that part of $servername that matches "*" is all part of a single
     # domain label.
     [[ -n $(echo -n "${servername:len_part1:len_wildcard}" | sed 's/^[_A-Z0-9\-]*//') ]] && return 1

     return 0
}

# Compare the server name provided in arg1 to the CN and SAN in arg2 and return:
#    0, if server name provided does not match any of the names in the CN or SAN
#    1, if the server name provided matches a name in the SAN
#    2, if the server name provided is a wildcard match against a name in the SAN
#    4, if the server name provided matches the CN
#    5, if the server name provided matches the CN AND a name in the SAN
#    6, if the server name provided matches the CN AND is a wildcard match against a name in the SAN
#    8, if the server name provided is a wildcard match against the CN
#    9, if the server name provided matches a name in the SAN AND is a wildcard match against the CN
#   10, if the server name provided is a wildcard match against the CN AND a name in the SAN

compare_server_name_to_cert() {
     local cert="$1"
     local servername cn dns_sans ip_sans san dercert tag
     local srv_id="" xmppaddr=""
     local -i i len len1
     local -i subret=0             # no error condition, passing results

     HAS_DNS_SANS=false
     if [[ -n "$XMPP_HOST" ]]; then
          # RFC 6120, Section 13.7.2.1, states that for XMPP the identity that
          # should appear in the server's certificate is identity that appears
          # in the the 'to' address that the client communicates in the initial
          # stream header.
          servername="$(toupper "$XMPP_HOST")"
     else
          servername="$(toupper "$NODE")"
     fi

     # Check whether any of the DNS names in the certificate match the servername
     dns_sans="$(get_san_dns_from_cert "$cert")"
     while read san; do
          if [[ -n "$san" ]]; then
               HAS_DNS_SANS=true
               [[ $(toupper "$san") == "$servername" ]] && subret=1 && break
          fi
     done <<< "$dns_sans"

     if [[ $subret -eq 0 ]]; then
          # Check whether any of the IP addresses in the certificate match the servername
          ip_sans=$($OPENSSL x509 -in "$cert" -noout -text 2>>$ERRFILE | grep -A2 "Subject Alternative Name" | \
                  tr ',' '\n' | grep "IP Address:" | sed -e 's/IP Address://g' -e 's/ //g')
          while read san; do
               [[ -n "$san" ]] && [[ "$san" == "$servername" ]] && subret=1 && break
          done <<< "$ip_sans"
     fi

     if [[ $subret -eq 0 ]] && [[ -n "$XMPP_HOST" ]]; then
          # For XMPP hosts, in addition to checking for a matching DNS name,
          # should also check for a matching SRV-ID or XmppAddr identifier.
          dercert="$($OPENSSL x509 -in "$cert" -outform DER 2>>$ERRFILE | hexdump -v -e '16/1 "%02X"')"
          # Look for the beginning of the subjectAltName extension. It
          # will begin with the OID (2.5.29.17 = 0603551D11). After the OID
          # there may be an indication that the extension is critical (0101FF).
          # Finally will be the tag indicating that the value of the extension is
          # encoded as an OCTET STRING (04).
          if [[ "$dercert" =~ 0603551D110101FF04 ]]; then
               dercert="${dercert##*0603551D110101FF04}"
          else
               dercert="${dercert##*0603551D1104}"
          fi
          # Skip over the encoding of the length of the OCTET STRING.
          if [[ "${dercert:0:1}" == "8" ]]; then
               i="${dercert:1:1}"
               i=2*$i+2
               dercert="${dercert:i}"
          else
               dercert="${dercert:2}"
          fi
          # Next byte should be a 30 (SEQUENCE).
          if [[ "${dercert:0:2}" == "30" ]]; then
               # Get the length of the subjectAltName extension and then skip
               # over the encoding of the length.
               if [[ "${dercert:2:1}" == "8" ]]; then
                    case "${dercert:3:1}" in
                         1) len=2*0x${dercert:4:2}; dercert="${dercert:6}" ;;
                         2) len=2*0x${dercert:4:4}; dercert="${dercert:8}" ;;
                         3) len=2*0x${dercert:4:6}; dercert="${dercert:10}" ;;
                         *) len=0 ;;
                    esac
               else
                    len=2*0x${dercert:2:2}
                    dercert="${dercert:4}"
               fi
               if [[ $len -ne 0 ]] && [[ $len -lt ${#dercert} ]]; then
                    # loop through all the names and extract the SRV-ID and XmppAddr identifiers
                    for (( i=0; i < len; i+=len_name )); do
                         tag="${dercert:i:2}"
                         i+=2
                         if [[ "${dercert:i:1}" == "8" ]]; then
                              i+=1
                              case "${dercert:i:1}" in
                                   1) i+=1; len_name=2*0x${dercert:i:2}; i+=2 ;;
                                   2) i+=1; len_name=2*0x${dercert:i:4}; i+=4 ;;
                                   3) i+=1; len_name=2*0x${dercert:i:6}; i+=4 ;;
                                   *) len=0 ;;
                              esac
                         else
                              len_name=2*0x${dercert:i:2}
                              i+=2
                         fi
                         if [[ "$tag" == "A0" ]]; then
                              # This is an otherName.
                              if [[ $len_name -gt 18 ]] && ( [[ "${dercert:i:20}" == "06082B06010505070805" ]] || \
                                   [[ "${dercert:i:20}" == "06082B06010505070807" ]] ); then
                                   # According to the OID, this is either an SRV-ID or XmppAddr.
                                   j=$i+20
                                   if [[ "${dercert:j:2}" == "A0" ]]; then
                                        j+=2
                                        if [[ "${dercert:j:1}" == "8" ]]; then
                                             j+=1
                                             j+=2*0x${dercert:j:1}+1
                                        else
                                             j+=2
                                        fi
                                        if ( [[ "${dercert:i:20}" == "06082B06010505070805" ]] && [[ "${dercert:j:2}" == "0C" ]] ) || \
                                           ( [[ "${dercert:i:20}" == "06082B06010505070807" ]] && [[ "${dercert:j:2}" == "16" ]] ); then
                                             # XmppAddr should be encoded as UTF8STRING (0C) and
                                             # SRV-ID should be encoded IA5STRING (16).
                                             j+=2
                                             if [[ "${dercert:j:1}" == "8" ]]; then
                                                  j+=1
                                                  case "${dercert:j:1}" in
                                                       1) j+=1; len1=2*0x${dercert:j:2}; j+=2 ;;
                                                       2) j+=1; len1=2*0x${dercert:j:4}; j+=4 ;;
                                                       3) j+=1; len1=2*0x${dercert:j:6}; j+=6 ;;
                                                       4) len1=0 ;;
                                                  esac
                                             else
                                                  len1=2*0x${dercert:j:2}
                                                  j+=2
                                             fi
                                             if [[ $len1 -ne 0 ]]; then
                                                  san="$(asciihex_to_binary "${dercert:j:len1}")"
                                                  if [[ "${dercert:i:20}" == "06082B06010505070805" ]]; then
                                                       xmppaddr+="$san "
                                                  else
                                                       srv_id+="$san "
                                                  fi
                                             fi
                                        fi
                                   fi
                              fi
                         fi
                    done
               fi
          fi
          [[ -n "$srv_id" ]] && HAS_DNS_SANS=true
          [[ -n "$xmppaddr" ]] && HAS_DNS_SANS=true
          while read -d " " san; do
               [[ -n "$san" ]] && [[ $(toupper "$san") == "_XMPP-SERVER.$servername" ]] && subret=1 && break
          done <<< "$srv_id"
          if [[ $subret -eq 0 ]]; then
               while read -d " " san; do
                    [[ -n "$san" ]] && [[ $(toupper "$san") == "$servername" ]] && subret=1 && break
               done <<< "$xmppaddr"
          fi
     fi

     # Check whether any of the DNS names in the certificate are wildcard names
     # that match the servername
     if [[ $subret -eq 0 ]]; then
          while read san; do
               [[ -n "$san" ]] || continue
               wildcard_match "$servername" "$san"
               [[ $? -eq 0 ]] && subret=2 && break
          done <<< "$dns_sans"
     fi

     cn="$(get_cn_from_cert "$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 $(sed 's/^[_\.a-zA-Z0-9*\-]*//' <<< "$cn") ]] && return $subret

     # Check whether the CN in the certificate matches the servername
     [[ $(toupper "$cn") == "$servername" ]] && subret+=4 && return $subret

     # Check whether the CN in the certificate is a wildcard name that matches
     # the servername
     wildcard_match "$servername" "$cn"
     [[ $? -eq 0 ]] && subret+=8
     return $subret
}

# This function determines whether the certificate (arg3) contains "visibility
# information" (see Section 4.3.3 of
# https://www.etsi.org/deliver/etsi_ts/103500_103599/10352303/01.01.01_60/ts_10352303v010101p.pdf .
etsi_etls_visibility_info() {
     local jsonID="$1"
     local spaces="$2"
     local cert="$3"
     local cert_txt="$4"
     local dercert tag
     local -a fingerprint=() access_description=()
     local -i i j len len1 len_name nr_visnames=0

     # If "visibility information" is present, it will appear in the subjectAltName
     # extension (0603551D11) as an otherName with OID 0.4.0.3523.3.1 (060604009B430301).
     # OpenSSL displays all names of type otherName as "othername:<unsupported>".
     # As certificates will rarely include a name encoded as an otherName, check the
     # text version of the certificate for "othername:<unsupported>" before calling
     # external functions to obtain the DER encoded certficate.
     if [[ "$cert_txt" =~ X509v3\ Subject\ Alternative\ Name:.*othername:\<unsupported\> ]]; then
          dercert="$($OPENSSL x509 -in "$cert" -outform DER 2>>$ERRFILE | hexdump -v -e '16/1 "%02X"')"
          if [[ "$dercert" =~ 0603551D110101FF04[0-9A-F]*060604009B430301 ]] || \
             [[ "$dercert" =~ 0603551D1104[0-9A-F]*060604009B430301 ]]; then
               # Look for the beginning of the subjectAltName extension. It
               # will begin with the OID (2.5.29.17 = 0603551D11). After the OID
               # there may be an indication that the extension is critical (0101FF).
               # Finally will be the tag indicating that the value of the extension is
               # encoded as an OCTET STRING (04).
               if [[ "$dercert" =~ 0603551D110101FF04 ]]; then
                    dercert="${dercert##*0603551D110101FF04}"
               else
                    dercert="${dercert##*0603551D1104}"
               fi
               # Skip over the encoding of the length of the OCTET STRING.
               if [[ "${dercert:0:1}" == 8 ]]; then
                    i="${dercert:1:1}"
                    i=2*$i+2
                    dercert="${dercert:i}"
               else
                    dercert="${dercert:2}"
               fi
               # Next byte should be a 30 (SEQUENCE).
               if [[ "${dercert:0:2}" == 30 ]]; then
                    # Get the length of the subjectAltName extension and then skip
                    # over the encoding of the length.
                    if [[ "${dercert:2:1}" == 8 ]]; then
                         case "${dercert:3:1}" in
                              1) len=2*0x${dercert:4:2}; dercert="${dercert:6}" ;;
                              2) len=2*0x${dercert:4:4}; dercert="${dercert:8}" ;;
                              3) len=2*0x${dercert:4:6}; dercert="${dercert:10}" ;;
                              *) len=0 ;;
                         esac
                    else
                         len=2*0x${dercert:2:2}
                         dercert="${dercert:4}"
                    fi
                    if [[ $len -ne 0 ]] && [[ $len -lt ${#dercert} ]]; then
                         # loop through all the names and extract the visibility information
                         for (( i=0; i < len; i+=len_name )); do
                              tag="${dercert:i:2}"
                              i+=2
                              if [[ "${dercert:i:1}" == 8 ]]; then
                                   i+=1
                                   case "${dercert:i:1}" in
                                        1) i+=1; len_name=2*0x${dercert:i:2}; i+=2 ;;
                                        2) i+=1; len_name=2*0x${dercert:i:4}; i+=4 ;;
                                        3) i+=1; len_name=2*0x${dercert:i:6}; i+=4 ;;
                                        *) len=0 ;;
                                   esac
                              else
                                   len_name=2*0x${dercert:i:2}
                                   i+=2
                              fi
                              [[ "$tag" == A0 ]] || continue
                              # This is an otherName.
                              [[ $len_name -gt 16 ]] || continue
                              [[ "${dercert:i:16}" == 060604009B430301 ]] || continue
                              # According to the OID, this is visibility information.
                              j=$i+16
                              # Skip over the tag (A0) and length for the otherName value.
                              [[ "${dercert:j:2}" == A0 ]] || continue
                              j+=2
                              if [[ "${dercert:j:1}" == 8 ]]; then
                                   j+=1
                                   j+=2*0x${dercert:j:1}+1
                              else
                                   j+=2
                              fi
                              # The value for this otherName is encoded as a SEQUENCE (30):
                              #    VisibilityInformation ::= SEQUENCE {
                              #         fingerprint         OCTET STRING (SIZE(10)),
                              #         accessDescription   UTF8String }
                              [[ "${dercert:j:2}" == 30 ]] || continue
                              j+=2
                              if [[ "${dercert:j:1}" == 8 ]]; then
                                   j+=1
                                   case "${dercert:j:1}" in
                                        1) j+=1; len1=2*0x${dercert:j:2}; j+=2 ;;
                                        2) j+=1; len1=2*0x${dercert:j:4}; j+=4 ;;
                                        3) j+=1; len1=2*0x${dercert:j:6}; j+=6 ;;
                                        4) len1=0 ;;
                                   esac
                              else
                                   len1=2*0x${dercert:j:2}
                                   j+=2
                              fi
                              [[ $len1 -ne 0 ]] || continue
                              # Next is the 10-byte fingerprint, encoded as an OCTET STRING (04)
                              [[ "${dercert:j:4}" == 040A ]] || continue
                              j+=4
                              fingerprint[nr_visnames]="$(asciihex_to_binary "${dercert:j:20}")"
                              j+=20
                              # Finally comes the access description, encoded as a UTF8String (0C).
                              [[ "${dercert:j:2}" == 0C ]] || continue
                              j+=2
                              if [[ "${dercert:j:1}" == "8" ]]; then
                                   j+=1
                                   case "${dercert:j:1}" in
                                        1) j+=1; len1=2*0x${dercert:j:2}; j+=2 ;;
                                        2) j+=1; len1=2*0x${dercert:j:4}; j+=4 ;;
                                        3) j+=1; len1=2*0x${dercert:j:6}; j+=6 ;;
                                        4) len1=0 ;;
                                   esac
                              else
                                   len1=2*0x${dercert:j:2}
                                   j+=2
                              fi
                              access_description[nr_visnames]=""$(asciihex_to_binary "${dercert:j:len1}")""
                              nr_visnames+=1
                         done
                    fi
               fi
          fi
     fi
     if [[ $nr_visnames -eq 0 ]]; then
          outln "not present"
          fileout "$jsonID" "INFO" "not present"
     else
          for (( i=0; i < nr_visnames; i++ )); do
               [[ $i -ne 0 ]] && out "$spaces"
               outln "$(out_row_aligned_max_width "${fingerprint[i]} / ${access_description[i]}" "$spaces" $TERM_WIDTH)"
               fileout "$jsonID" "INFO" "${fingerprint[i]} / ${access_description[i]}"
          done
     fi
     return 0
}

# NOTE: arg3 must contain the text output of $HOSTCERT.
must_staple() {
     local jsonID="cert_mustStapleExtension"
     local json_postfix="$1"
     local provides_stapling="$2"
     local hostcert_txt="$3"
     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.

     # OpenSSL 1.1.0 supports pretty-printing the "TLS Feature extension." For any
     # previous versions of OpenSSL, OpenSSL can only show if the extension OID is present.
     if grep -A 1 "TLS Feature:" <<< "$hostcert_txt" | grep -q "status_request"; then
          # FIXME: This will indicate that must staple is supported if the
          # certificate indicates status_request or status_request_v2. This is
          # probably okay, since it seems likely that any TLS Feature extension
          # that includes status_request_v2 will also include status_request.
          supported=true
     elif [[ "$hostcert_txt" =~ '1.3.6.1.5.5.7.1.24:' ]]; then
          cert="$($OPENSSL x509 -in "$HOSTCERT" -outform DER 2>>$ERRFILE | hexdump -v -e '16/1 "%02X"')"
          extn="${cert##*06082B06010505070118}"
          # Check for critical bit, and skip over it if present.
          [[ "${extn:0:6}" == "0101FF" ]] && extn="${extn:6}"
          # Next is tag and length of extnValue OCTET STRING. Assume it is less than 128 bytes.
          extn="${extn:4}"
          # The TLS Feature is a SEQUENCE of INTEGER. Get the length of the SEQUENCE
          extn_len=2*$(hex2dec "${extn:2:2}")
          # If the extension include the status_request (5), then it supports must staple.
          if [[ "${extn:4:extn_len}" =~ 020105 ]]; then
               supported=true
          fi
     fi

     if "$supported"; then
          if "$provides_stapling"; then
               prln_svrty_good "supported"
               fileout "${jsonID}${json_postfix}" "OK" "supported"
          else
               prln_svrty_high "requires OCSP stapling (NOT ok)"
               fileout "${jsonID}${json_postfix}" "HIGH" "extension detected but no OCSP stapling provided"
          fi
     else
          outln "--"
          fileout "${jsonID}${json_postfix}" "INFO" "--"
     fi
     return 0
}

# TODO: This function checks for Certificate Transparency support based on RFC 6962.
# It will need to be updated to add checks for Certificate Transparency support based on 6962bis.
# return values are results, no error conditions
certificate_transparency() {
     local cert_txt="$1"
     local ocsp_response="$2"
     local -i number_of_certificates=$3
     local cipher="$4"
     local sni_used="$5"
     local tls_version="$6"
     local sni=""
     local ciphers=""
     local hexc n ciph sslver kx auth enc mac export
     local extra_extns=""
     local -i success
     # Cipher suites that use a certifiate with an RSA (signature) public key
     local -r a_rsa="cc,13, cc,15, c0,30, c0,28, c0,14, 00,9f, cc,a8, cc,aa, c0,a3, c0,9f, 00,6b, 00,39, c0,77, 00,c4, 00,88, c0,45, c0,4d, c0,53, c0,61, c0,7d, c0,8b, 16,b7, 16,b9, c0,2f, c0,27, c0,13, 00,9e, c0,a2, c0,9e, 00,67, 00,33, c0,76, 00,be, 00,9a, 00,45, c0,44, c0,4c, c0,52, c0,60, c0,7c, c0,8a, c0,11, c0,12, 00,16, 00,15, 00,14, c0,10"
     # Cipher suites that use a certifiate with an RSA (encryption) public key
     local -r e_rsa="00,b7, c0,99, 00,ad, cc,ae, 00,9d, c0,a1, c0,9d, 00,3d, 00,35, 00,c0, 00,84, 00,95, c0,3d, c0,51, c0,69, c0,6f, c0,7b, c0,93, ff,01, 00,ac, c0,a0, c0,9c, 00,9c, 00,3c, 00,2f, 00,ba, 00,b6, 00,96, 00,41, c0,98, 00,07, 00,94, c0,3c, c0,50, c0,68, c0,6e, c0,7a, c0,92, 00,05, 00,04, 00,92, 00,0a, 00,93, fe,ff, ff,e0, 00,62, 00,09, 00,61, fe,fe, ff,e1, 00,64, 00,60, 00,08, 00,06, 00,03, 00,b9, 00,b8, 00,2e, 00,3b, 00,02, 00,01, ff,00"
     # Cipher suites that use a certifiate with a DSA public key
     local -r a_dss="00,a3, 00,6a, 00,38, 00,c3, 00,87, c0,43, c0,57, c0,81, 00,a2, 00,40, 00,32, 00,bd, 00,99, 00,44, c0,42, c0,56, c0,80, 00,66, 00,13, 00,63, 00,12, 00,65, 00,11"
     # Cipher suites that use a certifiate with a DH public key
     local -r a_dh="00,a5, 00,a1, 00,69, 00,68, 00,37, 00,36, 00,c2, 00,c1, 00,86, 00,85, c0,3f, c0,41, c0,55, c0,59, c0,7f, c0,83, 00,a4, 00,a0, 00,3f, 00,3e, 00,31, 00,30, 00,bc, 00,bb, 00,98, 00,97, 00,43, 00,42, c0,3e, c0,40, c0,54, c0,58, c0,7e, c0,82, 00,10, 00,0d, 00,0f, 00,0c, 00,0b, 00,0e"
     # Cipher suites that use a certifiate with an ECDH public key
     local -r a_ecdh="c0,32, c0,2e, c0,2a, c0,26, c0,0f, c0,05, c0,79, c0,75, c0,4b, c0,4f, c0,5f, c0,63, c0,89, c0,8d, c0,31, c0,2d, c0,29, c0,25, c0,0e, c0,04, c0,78, c0,74, c0,4a, c0,4e, c0,5e, c0,62, c0,88, c0,8c, c0,0c, c0,02, c0,0d, c0,03, c0,0b, c0,01"
     # Cipher suites that use a certifiate with an ECDSA public key
     local -r a_ecdsa="cc,14, c0,2c, c0,24, c0,0a, cc,a9, c0,af, c0,ad, c0,73, c0,49, c0,5d, c0,87, 16,b8, 16,ba, c0,2b, c0,23, c0,09, c0,ae, c0,ac, c0,72, c0,48, c0,5c, c0,86, c0,07, c0,08, c0,06"
     # Cipher suites that use a certifiate with a GOST public key
     local -r a_gost="00,80, 00,81, 00,82, 00,83"

     # First check whether signed certificate timestamps (SCT) are included in the
     # server's certificate. If they aren't, check whether the server provided
     # a stapled OCSP response with SCTs. If no SCTs were found in the certificate
     # or OCSP response, check for an SCT TLS extension.
     if [[ "$cert_txt" =~ CT\ Precertificate\ SCTs ]] || [[ "$cert_txt" =~ '1.3.6.1.4.1.11129.2.4.2' ]]; then
          tm_out "certificate extension"
          return 0
     fi
     if [[ "$ocsp_response" =~ CT\ Certificate\ SCTs ]] || [[ "$ocsp_response" =~ '1.3.6.1.4.1.11129.2.4.5' ]]; then
          tm_out "OCSP extension"
          return 0
     fi

     # If the server only has one certificate, then it is sufficient to check whether
     # determine_tls_extensions() discovered an SCT TLS extension. If the server has more than
     # one certificate, then it is possible that an SCT TLS extension is returned for some
     # certificates, but not for all of them.
     if [[ $number_of_certificates -eq 1 ]] && [[ "$TLS_EXTENSIONS" =~ signed\ certificate\ timestamps ]]; then
          tm_out "TLS extension"
          return 0
     fi

     if [[ $number_of_certificates -gt 1 ]] && ! "$SSL_NATIVE"; then
          if [[ "$tls_version" == 0304 ]]; then
               ciphers=", 13,01, 13,02, 13,03, 13,04, 13,05"
               if [[ "$cipher" == tls1_3_RSA ]]; then
                    extra_extns=", 00,0d,00,10,00,0e,08,04,08,05,08,06,04,01,05,01,06,01,02,01"
               elif [[ "$cipher" == tls1_3_ECDSA ]]; then
                    extra_extns=", 00,0d,00,0a,00,08,04,03,05,03,06,03,02,03"
               else
                    return 1
               fi
          else
               [[ "$cipher" =~ aRSA ]] && ciphers+=", $a_rsa"
               [[ "$cipher" =~ eRSA ]] && ciphers+=", $e_rsa"
               [[ "$cipher" =~ aDSS ]] && ciphers+=", $a_dss"
               [[ "$cipher" =~ aDH ]] && ciphers+=", $a_dh"
               [[ "$cipher" =~ aECDH ]] && ciphers+=", $a_ecdh"
               [[ "$cipher" =~ aECDSA ]] && ciphers+=", $a_ecdsa"
               [[ "$cipher" =~ aGOST ]] && ciphers+=", $a_gost"

               [[ -z "$ciphers" ]] && return 1
               ciphers+=", 00,ff"
          fi
          [[ -z "$sni_used" ]] && sni="$SNI" && SNI=""
          tls_sockets "${tls_version:2:2}" "${ciphers:2}" "all" "00,12,00,00$extra_extns"
          success=$?
          [[ -z "$sni_used" ]] && SNI="$sni"
          if ( [[ $success -eq 0 ]] || [[ $success -eq 2 ]] ) && \
             grep -a 'TLS server extension ' "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" | \
             grep -aq "signed certificate timestamps"; then
               tm_out "TLS extension"
               return 0
          fi
     fi

     if [[ $SERVICE != HTTP ]] && ! "$CLIENT_AUTH"; then
          # At the moment Certificate Transparency only applies to HTTPS.
          tm_out "N/A"
     else
          tm_out "--"
     fi
     return 0
}

certificate_info() {
     local proto
     local -i certificate_number=$1
     local -i number_of_certificates=$2
     local cert_txt="$3"
     local cipher=$4
     local cert_keysize=$5
     local cert_type="$6"
     local ocsp_response_binary="$7"
     local ocsp_response=$8
     local ocsp_response_status=$9
     local sni_used="${10}"
     local ct="${11}"
     local certificate_list_ordering_problem="${12}"
     local cert_sig_algo cert_sig_hash_algo cert_key_algo cert_spki_info
     local common_primes_file="$TESTSSL_INSTALL_DIR/etc/common-primes.txt"
     local -i lineno_matched=0
     local cert_keyusage cert_ext_keyusage short_keyAlgo
     local outok=true
     local expire days2expire secs2warn ocsp_uri crl
     local startdate enddate issuer_CN issuer_C issuer_O issuer sans san all_san="" cn
     local issuer_DC issuerfinding cn_nosni=""
     local cert_fingerprint_sha1 cert_fingerprint_sha2 cert_serial
     local policy_oid
     local spaces=""
     local -i trust_sni=0 trust_nosni=0 diffseconds=0
     local has_dns_sans has_dns_sans_nosni
     local trust_sni_finding
     local -i certificates_provided
     local cnfinding trustfinding trustfinding_nosni
     local cnok="OK"
     local expfinding expok="OK"
     local -i ret=0
     local json_postfix=""                   # string to place at the end of JSON IDs when there is more than one certificate
     local jsonID=""                         # string to place at beginning of JSON IDs
     local json_rating json_msg
     local indent=""
     local days2warn2=$DAYS2WARN2
     local days2warn1=$DAYS2WARN1
     local provides_stapling=false
     local caa_node="" all_caa="" caa_property_name="" caa_property_value=""
     local response=""
     local yearstart yearend clockstart clockend y m d
     local gt_825=false gt_825warn=false

     if [[ $number_of_certificates -gt 1 ]]; then
          [[ $certificate_number -eq 1 ]] && outln
          indent="  "
          out "$indent"
          pr_headline "Server Certificate #$certificate_number"
          [[ -z "$sni_used" ]] && pr_underline " (in response to request w/o SNI)"
          outln
          json_postfix=" <cert#${certificate_number}>"
          spaces="                                "
     else
          spaces="                              "
     fi

     GOOD_CA_BUNDLE=""
     cert_sig_algo="$(awk -F':' '/Signature Algorithm/ { print $2; if (++Match >= 1) exit; }' <<< "$cert_txt")"
     cert_sig_algo="${cert_sig_algo// /}"
     cert_key_algo="$(awk -F':' '/Public Key Algorithm:/ { print $2; if (++Match >= 1) exit; }' <<< "$cert_txt")"
     cert_key_algo="${cert_key_algo// /}"

     out "$indent" ; pr_bold " Signature Algorithm          "
     jsonID="cert_signatureAlgorithm"
     case $cert_sig_algo in
          sha1WithRSAEncryption)
               pr_svrty_medium "SHA1 with RSA"
               if [[ "$SERVICE" == HTTP ]] || "$ASSUME_HTTP"; then
                    out " -- besides: users will receive a "; pr_svrty_high "strong browser WARNING"
               fi
               outln
               fileout "${jsonID}${json_postfix}" "MEDIUM" "SHA1 with RSA"
               set_grade_cap "T" "Uses SHA1 algorithm"
               ;;
          sha224WithRSAEncryption)
               outln "SHA224 with RSA"
               fileout "${jsonID}${json_postfix}" "INFO" "SHA224 with RSA"
               ;;
          sha256WithRSAEncryption)
               prln_svrty_good "SHA256 with RSA"
               fileout "${jsonID}${json_postfix}" "OK" "SHA256 with RSA"
               ;;
          sha384WithRSAEncryption)
               prln_svrty_good "SHA384 with RSA"
               fileout "${jsonID}${json_postfix}" "OK" "SHA384 with RSA"
               ;;
          sha512WithRSAEncryption)
               prln_svrty_good "SHA512 with RSA"
               fileout "${jsonID}${json_postfix}" "OK" "SHA512 with RSA"
               ;;
          ecdsa-with-SHA1)
               prln_svrty_medium "ECDSA with SHA1"
               fileout "${jsonID}${json_postfix}" "MEDIUM" "ECDSA with SHA1"
               set_grade_cap "T" "Uses SHA1 algorithm"
               ;;
          ecdsa-with-SHA224)
               outln "ECDSA with SHA224"
               fileout "${jsonID}${json_postfix}" "INFO" "ECDSA with SHA224"
               ;;
          ecdsa-with-SHA256)
               prln_svrty_good "ECDSA with SHA256"
               fileout "${jsonID}${json_postfix}" "OK" "ECDSA with SHA256"
               ;;
          ecdsa-with-SHA384)
               prln_svrty_good "ECDSA with SHA384"
               fileout "${jsonID}${json_postfix}" "OK" "ECDSA with SHA384"
               ;;
          ecdsa-with-SHA512)
               prln_svrty_good "ECDSA with SHA512"
               fileout "${jsonID}${json_postfix}" "OK" "ECDSA with SHA512"
               ;;
          dsaWithSHA1)
               prln_svrty_medium "DSA with SHA1"
               fileout "${jsonID}${json_postfix}" "MEDIUM" "DSA with SHA1"
               set_grade_cap "T" "Uses SHA1 algorithm"
               ;;
          dsa_with_SHA224)
               outln "DSA with SHA224"
               fileout "${jsonID}${json_postfix}" "INFO" "DSA with SHA224"
               ;;
          dsa_with_SHA256)
               prln_svrty_good "DSA with SHA256"
               fileout "${jsonID}${json_postfix}" "OK" "DSA with SHA256"
               ;;
          rsassaPss)
               cert_sig_hash_algo="$(grep -A 1 "Signature Algorithm" <<< "$cert_txt" | head -2 | tail -1 | sed 's/^.*Hash Algorithm: //')"
               case $cert_sig_hash_algo in
                    sha1)
                         prln_svrty_medium "RSASSA-PSS with SHA1"
                         fileout "${jsonID}${json_postfix}" "MEDIUM" "RSASSA-PSS with SHA1"
                         set_grade_cap "T" "Uses SHA1 algorithm"
                         ;;
                    sha224)
                         outln "RSASSA-PSS with SHA224"
                         fileout "${jsonID}${json_postfix}" "INFO" "RSASSA-PSS with SHA224"
                         ;;
                    sha256)
                         prln_svrty_good "RSASSA-PSS with SHA256"
                         fileout "${jsonID}${json_postfix}" "OK" "RSASSA-PSS with SHA256"
                         ;;
                    sha384)
                         prln_svrty_good "RSASSA-PSS with SHA384"
                         fileout "${jsonID}${json_postfix}" "OK" "RSASSA-PSS with SHA384"
                         ;;
                    sha512)
                         prln_svrty_good "RSASSA-PSS with SHA512"
                         fileout "${jsonID}${json_postfix}" "OK" "RSASSA-PSS with SHA512"
                         ;;
                    *)
                         out "RSASSA-PSS with $cert_sig_hash_algo"
                         prln_warning " (Unknown hash algorithm)"
                         fileout "${jsonID}${json_postfix}" "DEBUG" "RSASSA-PSS with $cert_sig_hash_algo"
                    esac
                    ;;
          md2*)
               prln_svrty_critical "MD2"
               fileout "${jsonID}${json_postfix}" "CRITICAL" "MD2"
               set_grade_cap "F" "Supports a insecure signature (MD2)"
               ;;
          md4*)
               prln_svrty_critical "MD4"
               fileout "${jsonID}${json_postfix}" "CRITICAL" "MD4"
               ;;
          md5*)
               prln_svrty_critical "MD5"
               fileout "${jsonID}${json_postfix}" "CRITICAL" "MD5"
               set_grade_cap "F" "Supports a insecure signature (MD5)"
               ;;
          *)
               out "$cert_sig_algo ("
               pr_warning "FIXME: can't tell whether this is good or not"
               outln ")"
               fileout "${jsonID}${json_postfix}" "DEBUG" "$cert_sig_algo"
               ((ret++))
               ;;
     esac
     # old, but still interesting: https://blog.hboeck.de/archives/754-Playing-with-the-EFF-SSL-Observatory.html

     out "$indent"; pr_bold " Server key size              "
     jsonID="cert_keySize"
     if [[ -z "$cert_keysize" ]]; then
          outln "(couldn't determine)"
          fileout "${jsonID}${json_postfix}" "cannot be determined"
          ((ret++))
     else
          case $cert_key_algo in
               *RSA*|*rsa*)             short_keyAlgo="RSA";;
               *ecdsa*|*ecPublicKey)    short_keyAlgo="EC";;
               *DSA*|*dsa*)             short_keyAlgo="DSA";;
               *GOST*|*gost*)           short_keyAlgo="GOST";;
               *dh*|*DH*)               short_keyAlgo="DH" ;;
               *)                       pr_fixme "don't know $cert_key_algo "
                                        let ret++ ;;
          esac
          out "$short_keyAlgo "
          # https://tools.ietf.org/html/rfc4492,  https://www.keylength.com/en/compare/
          # https://doi.org/10.1007/s00145-001-0009-4
          # see https://csrc.nist.gov/publications/detail/sp/800-57-part-1/rev-4/final
          # Table 2 @ chapter 5.6.1 (~ p66)
          if [[ $cert_key_algo =~ ecdsa ]] || [[ $cert_key_algo =~ ecPublicKey ]]; then
               if [[ "$cert_keysize" -le 110 ]]; then       # a guess
                    pr_svrty_critical "$cert_keysize"
                    json_rating="CRITICAL"; json_msg="$short_keyAlgo $cert_keysize bits"
               elif [[ "$cert_keysize" -le 123 ]]; then     # a guess
                    pr_svrty_high "$cert_keysize"
                    json_rating="HIGH"; json_msg="$short_keyAlgo $cert_keysize bits"
               elif [[ "$cert_keysize" -le 163 ]]; then
                    pr_svrty_medium "$cert_keysize"
                    json_rating="MEDIUM"; json_msg="$short_keyAlgo $cert_keysize bits"
               elif [[ "$cert_keysize" -le 224 ]]; then
                    out "$cert_keysize"
                    json_rating="INFO"; json_msg="$short_keyAlgo $cert_keysize bits"
               elif [[ "$cert_keysize" -le 533 ]]; then
                    pr_svrty_good "$cert_keysize"
                    json_rating="OK"; json_msg="$short_keyAlgo $cert_keysize bits"
               else
                    out "keysize: $cert_keysize (not expected, FIXME)"
                    json_rating="DEBUG"; json_msg=" $cert_keysize bits (not expected)"
                    ((ret++))
               fi
               out " bits"

               set_key_str_score "$short_keyAlgo" "$cert_keysize" # TODO: should be $dh_param_size
          elif [[ $cert_key_algo =~ RSA ]] || [[ $cert_key_algo =~ rsa ]] || [[ $cert_key_algo =~ dsa ]] || \
               [[ $cert_key_algo =~ dhKeyAgreement ]] || [[ $cert_key_algo == X9.42\ DH ]]; then
               if [[ "$cert_keysize" -le 512 ]]; then
                    pr_svrty_critical "$cert_keysize"
                    out " bits"
                    json_rating="CRITICAL"; json_msg="$short_keyAlgo $cert_keysize bits"
               elif [[ "$cert_keysize" -le 768 ]]; then
                    pr_svrty_high "$cert_keysize"
                    out " bits"
                    json_rating="HIGH"; json_msg="$short_keyAlgo $cert_keysize bits"
               elif [[ "$cert_keysize" -le 1024 ]]; then
                    pr_svrty_medium "$cert_keysize"
                    out " bits"
                    json_rating="MEDIUM"; json_msg="$short_keyAlgo $cert_keysize bits"
               elif [[ "$cert_keysize" -le 2048 ]]; then
                    out "$cert_keysize bits"
                    json_rating="INFO"; json_msg="$short_keyAlgo $cert_keysize bits"
               elif [[ "$cert_keysize" -le 4096 ]]; then
                    pr_svrty_good "$cert_keysize"
                    json_rating="OK"; json_msg="$short_keyAlgo $cert_keysize bits"
                    out " bits"
               else
                    pr_warning "weird key size: $cert_keysize bits"; out " (could cause compatibility problems)"
                    json_rating="WARN"; json_msg="$cert_keysize bits (Odd)"
                    ((ret++))
               fi

               set_key_str_score "$short_keyAlgo" "$cert_keysize"
          else
               out "$cert_key_algo + $cert_keysize bits ("
               pr_warning "FIXME: can't tell whether this is good or not"
               out ")"
               json_rating="WARN"; json_msg="Server keys $cert_keysize bits, unknown public key algorithm $cert_key_algo"
               ((ret++))
          fi
     fi

     case "$short_keyAlgo" in
          "RSA") cert_spki_info="${cert_txt##*Subject Public Key Info:}"
                 cert_spki_info="${cert_spki_info##*Public Key Algorithm:}"
                 cert_spki_info="${cert_spki_info#*Exponent:}"
                 cert_spki_info="$(strip_leading_space "$cert_spki_info")"
                 cert_spki_info="${cert_spki_info%%[[:space:]]*}"
                 if [[ -n "$cert_spki_info" ]]; then
                      if [[ $cert_spki_info -eq 1 ]]; then
                           out " (exponent is "; pr_svrty_critical "$cert_spki_info"; out ")"
                           json_rating="CRITICAL"
                           set_grade_cap "F" "RSA certificate uses exponent of 1"
                      else
                           out " (exponent is $cert_spki_info)"
                      fi
                      json_msg+=" (exponent is $cert_spki_info)"
                 fi
                 ;;
          "EC")  cert_spki_info="${cert_txt##*Subject Public Key Info:}"
                 cert_spki_info="${cert_spki_info##*Public Key Algorithm:}"
                 cert_spki_info="${cert_spki_info##*ASN1 OID: }"
                 [[ "$cert_spki_info" =~ NIST\ CURVE:\  ]] && cert_spki_info="${cert_spki_info##*NIST CURVE: }"
                 cert_spki_info="${cert_spki_info%%[[:space:]]*}"
                 cert_spki_info="$(strip_lf "$(strip_spaces "$cert_spki_info")")"
                 if [[ -n "$cert_spki_info" ]]; then
                      out " (curve $cert_spki_info)"
                      json_msg+=" (curve $cert_spki_info)"
                 fi
                 ;;
          "DH")  if [[ -s "$common_primes_file" ]]; then
                      cert_spki_info="${cert_txt##*Subject Public Key Info:}"
                      cert_spki_info="${cert_spki_info##*Public Key Algorithm:}"
                      cert_spki_info="$(awk '/prime:|prime P:/,/generator:|generator G:/' <<< "$cert_spki_info" | grep -Ev "prime|generator")"
                      cert_spki_info="$(strip_spaces "$(colon_to_spaces "$(newline_to_spaces "$cert_spki_info")")")"
                      [[ "${cert_spki_info:0:2}" == 00 ]] && cert_spki_info="${cert_spki_info:2}"
                      cert_spki_info="$(toupper "$cert_spki_info")"
                      lineno_matched=$(grep -n "$cert_spki_info" "$common_primes_file" 2>/dev/null | awk -F':' '{ print $1 }')
                      if [[ "$lineno_matched" -ne 0 ]]; then
                           cert_spki_info="$(awk "NR == $lineno_matched-1" "$common_primes_file" | awk -F'"' '{ print $2 }')"
                           out " ($cert_spki_info)"
                           json_msg+=" ($cert_spki_info)"
                      fi
                 fi
                 ;;
     esac
     outln
     fileout "${jsonID}${json_postfix}" "$json_rating" "$json_msg"

     out "$indent"; pr_bold " Server key usage             ";
     outok=true
     jsonID="cert_keyUsage"
     cert_keyusage="$(strip_leading_space "$(awk '/X509v3 Key Usage:/ { getline; print $0 }' <<< "$cert_txt")")"
     if [[ -n "$cert_keyusage" ]]; then
          outln "$cert_keyusage"
          if ( [[ " $cert_type " =~ " RSASig " ]] || [[ " $cert_type " =~ " DSA " ]] || [[ " $cert_type " =~ " ECDSA " ]] ) && \
             [[ ! "$cert_keyusage" =~ "Digital Signature" ]]; then
               prln_svrty_high "$indent                              Certificate incorrectly used for digital signatures"
               fileout "${jsonID}${json_postfix}" "HIGH" "Certificate incorrectly used for digital signatures: \"$cert_keyusage\""
               outok=false
          fi
          if [[ " $cert_type " =~ " RSAKMK " ]] && [[ ! "$cert_keyusage" =~ "Key Encipherment" ]]; then
               prln_svrty_high "$indent                              Certificate incorrectly used for key encipherment"
               fileout "${jsonID}${json_postfix}" "HIGH" "Certificate incorrectly used for key encipherment: \"$cert_keyusage\""
               outok=false
          fi
          if ( [[ " $cert_type " =~ " DH " ]] || [[ " $cert_type " =~ " ECDH " ]] ) && \
             [[ ! "$cert_keyusage" =~ "Key Agreement" ]]; then
               prln_svrty_high "$indent                              Certificate incorrectly used for key agreement"
               fileout "${jsonID}${json_postfix}" "HIGH" "Certificate incorrectly used for key agreement: \"$cert_keyusage\""
               outok=false
          fi
     else
          outln "--"
          fileout "${jsonID}${json_postfix}" "INFO" "No server key usage information"
          outok=false
     fi
     if "$outok"; then
          fileout "${jsonID}${json_postfix}" "INFO" "$cert_keyusage"
     fi

     out "$indent"; pr_bold " Server extended key usage    ";
     jsonID="cert_extKeyUsage"
     outok=true
     cert_ext_keyusage="$(strip_leading_space "$(awk '/X509v3 Extended Key Usage:/ { getline; print $0 }' <<< "$cert_txt")")"
     if [[ -n "$cert_ext_keyusage" ]]; then
          outln "$cert_ext_keyusage"
          if [[ ! "$cert_ext_keyusage" =~ "TLS Web Server Authentication" ]] && [[ ! "$cert_ext_keyusage" =~ "Any Extended Key Usage" ]]; then
               prln_svrty_high "$indent                              Certificate incorrectly used for TLS Web Server Authentication"
               fileout "${jsonID}${json_postfix}" "HIGH" "Certificate incorrectly used for TLS Web Server Authentication: \"$cert_ext_keyusage\""
               outok=false
          fi
     else
          outln "--"
          fileout "${jsonID}${json_postfix}" "INFO" "No server extended key usage information"
          outok=false
     fi
     if "$outok"; then
          fileout "${jsonID}${json_postfix}" "INFO" "cert_ext_keyusage"
     fi

     out "$indent"; pr_bold " Serial / Fingerprints        "
     cert_serial="$($OPENSSL x509 -noout -in $HOSTCERT -serial 2>>$ERRFILE | sed 's/serial=//')"
     fileout "cert_serialNumber${json_postfix}" "INFO" "$cert_serial"

     cert_fingerprint_sha1="$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha1 2>>$ERRFILE | sed 's/Fingerprint=//' | sed 's/://g')"
     fileout "cert_fingerprintSHA1${json_postfix}" "INFO" "${cert_fingerprint_sha1//SHA1 /}"
     outln "$cert_serial / $cert_fingerprint_sha1"

     cert_fingerprint_sha2="$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha256 2>>$ERRFILE | sed 's/Fingerprint=//' | sed 's/://g' )"
     fileout "cert_fingerprintSHA256${json_postfix}" "INFO" "${cert_fingerprint_sha2//SHA256 /}"
     outln "$spaces$cert_fingerprint_sha2"

     # " " needs to be converted back to lf in JSON/CSV output
     fileout "cert${json_postfix}" "INFO" "$(< $HOSTCERT)"

     [[ -z $CERT_FINGERPRINT_SHA2 ]] && \
          CERT_FINGERPRINT_SHA2="$cert_fingerprint_sha2" ||
          CERT_FINGERPRINT_SHA2="$cert_fingerprint_sha2 $CERT_FINGERPRINT_SHA2"
     [[ -z $RSA_CERT_FINGERPRINT_SHA2 ]] && \
          ( [[ $cert_key_algo = *RSA* ]] || [[ $cert_key_algo = *rsa* ]] ) &&
          RSA_CERT_FINGERPRINT_SHA2="$cert_fingerprint_sha2"

     out "$indent"; pr_bold " Common Name (CN)             "
     cnfinding="Common Name (CN) : "
     cn="$(get_cn_from_cert $HOSTCERT)"
     if [[ -n "$cn" ]]; then
          pr_italic "$cn"
          cnfinding="$cn"
     else
          cn="no CN field in subject"
          out "($cn)"
          cnfinding="$cn"
          cnok="INFO"
     fi
     fileout "cert_commonName${json_postfix}" "$cnok" "$cnfinding"
     cnfinding=""

     if [[ -n "$sni_used" ]]; then
          if grep -q "\-\-\-\-\-BEGIN" "$HOSTCERT.nosni"; then
               cn_nosni="$(get_cn_from_cert "$HOSTCERT.nosni")"
               [[ -z "$cn_nosni" ]] && cn_nosni="no CN field in subject"
          fi
          debugme tm_out "\"$NODE\" | \"$cn\" | \"$cn_nosni\""
     else
          debugme tm_out "\"$NODE\" | \"$cn\""
     fi

     if [[ -z "$sni_used" ]] || [[ "$(toupper "$cn_nosni")" == "$(toupper "$cn")" ]]; then
          outln
          cnfinding="$cn"
     elif [[ -z "$cn_nosni" ]]; then
          out " (request w/o SNI didn't succeed";
          cnfinding+="request w/o SNI didn't succeed"
          if [[ $cert_sig_algo =~ ecdsa ]]; then
               out ", usual for EC certificates"
               cnfinding+=", usual for EC certificates"
          fi
          outln ")"
          cnfinding+=""
     elif [[ "$cn_nosni" == *"no CN field"* ]]; then
          outln ", (request w/o SNI: $cn_nosni)"
          cnfinding="$cn_nosni"
     else
          out " (CN in response to request w/o SNI: "; pr_italic "$cn_nosni"; outln ")"
          cnfinding="$cn_nosni"
     fi
     fileout "cert_commonName_wo_SNI${json_postfix}" "INFO" "$cnfinding"

     sans=$(grep -A2 "Subject Alternative Name" <<< "$cert_txt" | \
          grep -E "DNS:|IP Address:|email:|URI:|DirName:|Registered ID:" | tr ',' '\n' | \
          sed -e 's/ *DNS://g' -e 's/ *IP Address://g' -e 's/ *email://g' -e 's/ *URI://g' -e 's/ *DirName://g' \
              -e 's/ *Registered ID://g' \
              -e 's/ *othername:<unsupported>//g' -e 's/ *X400Name:<unsupported>//g' -e 's/ *EdiPartyName:<unsupported>//g')
          #           ^^^ CACert

     out "$indent"; pr_bold " subjectAltName (SAN)         "
     jsonID="cert_subjectAltName"
     if [[ -n "$sans" ]]; then
          while read san; do
               [[ -n "$san" ]] && all_san+="$san "
          done <<< "$sans"
          prln_italic "$(out_row_aligned_max_width "$all_san" "$indent                              " $TERM_WIDTH)"
          fileout "${jsonID}${json_postfix}" "INFO" "$all_san"
     else
          if [[ $SERVICE == "HTTP" ]] || "$ASSUME_HTTP"; then
               pr_svrty_high "missing (NOT ok)"; outln " -- Browsers are complaining"
               fileout "${jsonID}${json_postfix}" "HIGH" "No SAN, browsers are complaining"
          else
               pr_svrty_medium "missing"; outln " -- no SAN is deprecated"
               fileout "${jsonID}${json_postfix}" "MEDIUM" "Providing no SAN is deprecated"
          fi
     fi

     out "$indent"; pr_bold " Issuer                       "
     jsonID="cert_caIssuers"
     #FIXME: oid would be better maybe (see above)
     issuer="$($OPENSSL x509 -in  $HOSTCERT -noout -issuer -nameopt multiline,-align,sname,-esc_msb,utf8,-space_eq 2>>$ERRFILE)"
     issuer_CN="$(awk -F'=' '/CN=/ { print $2 }' <<< "$issuer")"
     issuer_O="$(awk -F'=' '/O=/ { print $2 }' <<< "$issuer")"
     issuer_C="$(awk -F'=' '/ C=/ { print $2 }' <<< "$issuer")"
     issuer_DC="$(awk -F'=' '/DC=/ { print $2 }' <<< "$issuer")"

     if [[ "$issuer_O" == "issuer=" ]] || [[ "$issuer_O" == "issuer= " ]] || [[ "$issuer_CN" == "$cn" ]]; then
          prln_svrty_critical "self-signed (NOT ok)"
          fileout "${jsonID}${json_postfix}" "CRITICAL" "selfsigned"
          set_grade_cap "T" "Self-signed certificate"
     else
          issuerfinding="$issuer_CN"
          pr_italic "$issuer_CN"
          if [[ -z "$issuer_O" ]] && [[ -n "$issuer_DC" ]]; then
               for san in $issuer_DC; do
                    if [[ -z "$issuer_O" ]]; then
                         issuer_O="${san}"
                    else
                         issuer_O="${san}.${issuer_O}"
                    fi
               done
          fi
          if [[ -n "$issuer_O" ]]; then
               issuerfinding+=" ("
               out " ("
               issuerfinding+="$issuer_O"
               pr_italic "$issuer_O"
               if [[ -n "$issuer_C" ]]; then
                    issuerfinding+=" from "
                    out " from "
                    issuerfinding+="$issuer_C"
                    pr_italic "$issuer_C"
               fi
               issuerfinding+=")"
               out ")"
          fi
          outln
          fileout "${jsonID}${json_postfix}" "INFO" "$issuerfinding"
     fi

     out "$indent"; pr_bold " Trust (hostname)             "
     compare_server_name_to_cert "$HOSTCERT"
     trust_sni=$?

     # Find out if the subjectAltName extension is present and contains
     # a DNS name, since Section 6.3 of RFC 6125 says:
     #      Security Warning: A client MUST NOT seek a match for a reference
     #      identifier of CN-ID if the presented identifiers include a DNS-ID,
     #      SRV-ID, URI-ID, or any application-specific identifier types
     #      supported by the client.
     has_dns_sans=$HAS_DNS_SANS

     case $trust_sni in
          0) trustfinding="certificate does not match supplied URI"
             set_grade_cap "M" "Domain name mismatch"
             ;;
          1) trustfinding="Ok via SAN" ;;
          2) trustfinding="Ok via SAN wildcard" ;;
          4) if "$has_dns_sans"; then
                  trustfinding="via CN, but not SAN"
             else
                  trustfinding="via CN only"
             fi
             ;;
          5) trustfinding="Ok via SAN and CN" ;;
          6) trustfinding="Ok via SAN wildcard and CN"
             ;;
          8) if "$has_dns_sans"; then
                  trustfinding="via CN wildcard, but not SAN"
             else
                  trustfinding="via CN (wildcard) only"
             fi
             ;;
          9) trustfinding="Ok via CN wildcard and SAN"
             ;;
         10) trustfinding="Ok via SAN wildcard and CN wildcard"
             ;;
     esac

     if [[ $trust_sni -eq 0 ]]; then
          pr_svrty_high "$trustfinding"
          trust_sni_finding="HIGH"
     elif ( [[ $trust_sni -eq 4 ]] || [[ $trust_sni -eq 8 ]] ); then
          if [[ $SERVICE == "HTTP" ]] || "$ASSUME_HTTP"; then
               # https://bugs.chromium.org/p/chromium/issues/detail?id=308330
               # https://bugzilla.mozilla.org/show_bug.cgi?id=1245280
               # https://www.chromestatus.com/feature/4981025180483584
               pr_svrty_high "$trustfinding"; out " -- Browsers are complaining"
               trust_sni_finding="HIGH"
          else
               pr_svrty_medium "$trustfinding"
               trust_sni_finding="MEDIUM"
               # we punish CN matching for non-HTTP as it is deprecated https://tools.ietf.org/html/rfc2818#section-3.1
               ! "$has_dns_sans" && out " -- CN only match is deprecated"
          fi
     else
          pr_svrty_good "$trustfinding"
          trust_sni_finding="OK"
     fi

     if [[ -n "$cn_nosni" ]]; then
          compare_server_name_to_cert "$HOSTCERT.nosni"
          trust_nosni=$?
          has_dns_sans_nosni=$HAS_DNS_SANS
     fi

     # See issue #733.
     if [[ -z "$sni_used" ]]; then
          trustfinding_nosni=""
     elif ( [[ $trust_sni -eq $trust_nosni ]] && [[ "$has_dns_sans" == "$has_dns_sans_nosni" ]] ) || \
          ( [[ $trust_sni -eq 0 ]] && [[ $trust_nosni -eq 0 ]] ); then
          trustfinding_nosni=" (same w/o SNI)"
     elif [[ $trust_nosni -eq 0 ]]; then
          if [[ $trust_sni -eq 4 ]] || [[ $trust_sni -eq 8 ]]; then
               trustfinding_nosni=" (w/o SNI: certificate does not match supplied URI)"
          else
               trustfinding_nosni=" (SNI mandatory)"
          fi
     elif [[ $trust_nosni -eq 4 ]] || [[ $trust_nosni -eq 8 ]] || [[ $trust_sni -eq 4 ]] || [[ $trust_sni -eq 8 ]]; then
          case $trust_nosni in
               1) trustfinding_nosni="(w/o SNI: Ok via SAN)" ;;
               2) trustfinding_nosni="(w/o SNI: Ok via SAN wildcard)" ;;
               4) if "$has_dns_sans_nosni"; then
                       trustfinding_nosni="(w/o SNI: via CN, but not SAN)"
                  else
                       trustfinding_nosni="(w/o SNI: via CN only)"
                  fi
                  ;;
               5) trustfinding_nosni="(w/o SNI: Ok via SAN and CN)" ;;
               6) trustfinding_nosni="(w/o SNI: Ok via SAN wildcard and CN)" ;;
               8) if "$has_dns_sans_nosni"; then
                       trustfinding_nosni="(w/o SNI: via CN wildcard, but not SAN)"
                  else
                       trustfinding_nosni="(w/o SNI: via CN (wildcard) only)"
                  fi
                  ;;
               9) trustfinding_nosni="(w/o SNI: Ok via CN wildcard and SAN)" ;;
              10) trustfinding_nosni="(w/o SNI: Ok via SAN wildcard and CN wildcard)" ;;
          esac
     elif [[ $trust_sni -ne 0 ]]; then
          trustfinding_nosni=" (works w/o SNI)"
     else
          trustfinding_nosni=" (however, works w/o SNI)"
     fi
     if [[ -n "$sni_used" ]] || [[ $trust_nosni -eq 0 ]] || ( [[ $trust_nosni -ne 4 ]] && [[ $trust_nosni -ne 8 ]] ); then
          outln "$trustfinding_nosni"
     elif [[ $SERVICE == "HTTP" ]] || "$ASSUME_HTTP"; then
          prln_svrty_high "$trustfinding_nosni"
     else
          prln_svrty_medium "$trustfinding_nosni"
     fi

     fileout "Certificate trust${json_postfix}" "$trust_sni_finding" "A certificate name mismatch error is typically encountered when a browser is unable to establish a secure connection to a website due to a mismatch between the domain name in the certificate and the domain name in the URL that the user is trying to access.\nResolution:\nTo resolve this issue, you should check that the certificate is installed correctly on the server and that it is issued for the correct domain name. If the certificate was issued for a different domain name or a different subdomain, you will need to obtain a new certificate for the correct domain name. If the certificate is self-signed, it is not trusted by the browser and the user will receive an error message."

     out "$indent"; pr_bold " Chain of trust"; out "               "
     jsonID="Certificate chain of trust"
     if [[ "$issuer_O" =~ StartCom ]] || [[ "$issuer_O" =~ WoSign ]] || [[ "$issuer_CN" =~ StartCom ]] || [[ "$issuer_CN" =~ WoSign ]]; then
          # Shortcut for this special case here.
          pr_italic "WoSign/StartCom"; out " are " ; prln_svrty_critical "not trusted anymore (NOT ok)"
          fileout "${jsonID}${json_postfix}" "CRITICAL" "Issuer not trusted anymore (WoSign/StartCom)"
          set_grade_cap "T" "Untrusted certificate chain"
     else
          # Also handles fileout, keep error if happened
          determine_trust "$jsonID" "$json_postfix" || ((ret++))
     fi

     # https://fahrplan.events.ccc.de/congress/2010/Fahrplan/attachments/1777_is-the-SSLiverse-a-safe-place.pdf, see p40+
     out "$indent"; pr_bold " EV cert"; out " (experimental)       "
     jsonID="cert_certificatePolicies_EV"
     # only the first one, seldom we have two
     policy_oid=$(awk '/ .Policy: / { print $2 }' <<< "$cert_txt" | awk 'NR < 2')
     if grep -Eq 'Extended Validation|Extended Validated|EV SSL|EV CA' <<< "$issuer" || \
          [[ 2.16.840.1.114028.10.1.2 == "$policy_oid" ]] || \
          [[ 2.16.840.1.114412.1.3.0.2 == "$policy_oid" ]] || \
          [[ 2.16.840.1.114412.2.1 == "$policy_oid" ]] || \
          [[ 2.16.578.1.26.1.3.3 == "$policy_oid" ]] || \
          [[ 1.3.6.1.4.1.17326.10.14.2.1.2 == "$policy_oid" ]] || \
          [[ 1.3.6.1.4.1.17326.10.8.12.1.2 == "$policy_oid" ]] || \
          [[ 1.3.6.1.4.1.13177.10.1.3.10 == "$policy_oid" ]] ; then
          out "yes "
          fileout "${jsonID}${json_postfix}" "OK" "yes"
     else
          out "no "
          fileout "${jsonID}${json_postfix}" "INFO" "no"
     fi
     debugme echo "($(newline_to_spaces "$policy_oid"))"
     outln
#TODO: check browser OIDs:
#         https://dxr.mozilla.org/mozilla-central/source/security/certverifier/ExtendedValidation.cpp
#         https://chromium.googlesource.com/chromium/chromium/+/master/net/base/ev_root_ca_metadata.cc
#         https://certs.opera.com/03/ev-oids.xml
#         see #967

     out "$indent"; pr_bold " ETS/\"eTLS\""
     out ", visibility info  "
     jsonID="cert_eTLS"
     etsi_etls_visibility_info "$jsonID" "$spaces" "$HOSTCERT" "$cert_txt"
     # *Currently* this is even listed as a vulnerability (CWE-310, CVE-2019-919), see
     # https://nvd.nist.gov/vuln/detail/CVE-2019-9191, https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9191
     # For now we leave this here. We may want to change that later or add infos to other sections (FS & vulnerability)

     out "$indent"; pr_bold " Certificate Validity (UTC)   "
     # FreeBSD + OSX can't swallow the leading blank:
     startdate="${cert_txt#*Validity*Not Before: }"
     startdate="${startdate%%GMT*}GMT"
     enddate="${cert_txt#*Validity*Not Before: *Not After : }"
     enddate="${enddate%%GMT*}GMT"
     debugme echo "$enddate - $startdate"
     # Now we have a normalized enddate and startdate like "Feb 27 10:03:20 2017 GMT" -- also for OpenBSD
     if "$HAS_OPENBSDDATE"; then
          # Best we want to do under old versions of OpenBSD, first just remove the GMT and keep start/endate for later output
          startdate="$(parse_date "$startdate" "+%s")"
          enddate="$(parse_date "$enddate" "+%s")"
          # Now we extract a date block and a time block which we need for later output
          startdate="$(parse_date "$startdate" +"%F %H:%M" "%b %d %T %Y %Z")"
          enddate="$(parse_date "$enddate" +"%F %H:%M" "%b %d %T %Y %Z")"
          read yearstart clockstart <<< "$startdate"
          read yearend clockend <<< "$enddate"
          debugme echo "$yearstart, $clockstart"
          debugme echo "$yearend, $clockend"
          y=$(( ${yearend:0:4} - ${yearstart:0:4} ))
          m=$(( ${yearend:5:1} - ${yearstart:5:1} + ${yearend:6:1} - ${yearstart:6:1} ))
          d=$(( ${yearend:8:2} - ${yearstart:8:2} ))
          # We take the year, month, days here as old OpenBSD's date is too difficult for real conversion
          # see comment in parse_date(). In diffseconds then we have the estimated absolute validity period
          diffseconds=$(( d + ((m*30)) + ((y*365)) ))
          diffseconds=$((diffseconds * 3600 * 24))
          # Now we estimate the days left plus length of month/year:
          yearnow="$(date -juz GMT "+%Y-%m-%d %H:%M")"
          y=$(( ${yearend:0:4} - ${yearnow:0:4} ))
          m=$(( ${yearend:5:1} - ${yearnow:5:1} + ${yearend:6:1} - ${yearnow:6:1} ))
          d=$(( ${yearend:8:2} - ${yearnow:8:2} ))
          days2expire=$(( d + ((m*30)) + ((y*365)) ))
     else
          startdate="$(parse_date "$startdate" +"%F %H:%M" "%b %d %T %Y %Z")"
          enddate="$(parse_date "$enddate" +"%F %H:%M" "%b %d %T %Y %Z")"
          days2expire=$(( $(parse_date "$enddate" "+%s" $'%F %H:%M') - $(LC_ALL=C date "+%s") ))  # first in seconds
          days2expire=$((days2expire  / 3600 / 24 ))
          diffseconds=$(( $(parse_date "$enddate" "+%s" $'%F %H:%M') - $(parse_date "$startdate" "+%s" $'%F %H:%M') ))
     fi
     # We adjust the thresholds by %50 for LE certificates, relaxing warnings for those certificates.
     # . instead of \' because it does not break syntax highlighting in vim
     if [[ "$issuer_CN" =~ ^Let.s\ Encrypt\ Authority ]] ; then
          days2warn2=$((days2warn2 / 2))
          days2warn1=$((days2warn1 / 2))
     fi

     debugme echo -n "diffseconds: $diffseconds"
     expire=$($OPENSSL x509 -in $HOSTCERT -checkend 1 2>>$ERRFILE)
     if ! grep -qw not <<< "$expire" ; then
          pr_svrty_critical "expired"
          expfinding="expired"
          expok="CRITICAL"
          set_grade_cap "T" "Certificate expired"
     else
          secs2warn=$((24 * 60 * 60 * days2warn2))          # low threshold first
          expire=$($OPENSSL x509 -in $HOSTCERT -checkend $secs2warn 2>>$ERRFILE)
          if grep -qw not <<< "$expire"; then
               secs2warn=$((24 * 60 * 60 * days2warn1))     # high threshold
               expire=$($OPENSSL x509 -in $HOSTCERT -checkend $secs2warn 2>>$ERRFILE)
               if grep -qw not <<< "$expire"; then
                    pr_svrty_good "$days2expire >= $days2warn1 days"
                    expfinding+="$days2expire >= $days2warn1 days"
               else
                    pr_svrty_medium "expires < $days2warn1 days ($days2expire)"
                    expfinding+="expires < $days2warn1 days ($days2expire)"
                    expok="MEDIUM"
               fi
          else
               pr_svrty_high "expires < $days2warn2 days ($days2expire)"
               expfinding+="expires < $days2warn2 days ($days2expire)"
               expok="HIGH"
          fi
     fi
     outln " ($startdate --> $enddate)"
     fileout "Certficate expiration${json_postfix}" "$expok" "Certificate expiration status: $expfinding"
     fileout "Certificate not valid before${json_postfix}" "INFO" "Certificate not valid before: $startdate"      # we assume that the certificate has no start time in the future
     fileout "Certificate not valid after${json_postfix}" "$expok" "Certificate not valid after: $enddate"       # They are in UTC

     # Internal certificates or those from appliances often have too high validity periods.
     # We check for ~10 years and >~ 5 years
     if [[ $diffseconds -ge $((3600 * 24 * 365 * 10)) ]]; then
          out "$spaces"
          prln_svrty_high ">= 10 years is way too long"
          fileout "cert_validityPeriod${json_postfix}" "HIGH" "$((diffseconds / (3600 * 24) )) days"
     elif [[ $diffseconds -ge $((3600 * 24 * 365 * 5)) ]]; then
          out "$spaces"
          prln_svrty_medium ">= 5 years is too long"
          fileout "cert_validityPeriod${json_postfix}" "MEDIUM" "$((diffseconds / (3600 * 24) )) days"
     elif [[ $diffseconds -ge $((3600 * 24 * 825 + 1)) ]]; then
     # Also "official" certificates issued from March 1st, 2018 (1517353200) aren't supposed
     # to be valid longer than 825 days which is 1517353200 in epoch seconds
          gt_825=true
          if "$HAS_OPENBSDDATE"; then
               if [[ 20180301 -le ${yearstart//-/} ]]; then
                    gt_825warn=true
               fi
          elif [[ $(parse_date "$startdate" "+%s" $'%F %H:%M') -ge 1517353200 ]]; then
               gt_825warn=true
          fi
          # Now, the verdict, depending on the issuing date
          out "$spaces"
          if "$gt_825warn" && "$gt_825"; then
               prln_svrty_medium "> 825 days issued after 2018/03/01 is too long"
               fileout "cert_validityPeriod${json_postfix}" "MEDIUM" "$((diffseconds / (3600 * 24) )) > 825 days"
          elif "$gt_825"; then
               outln ">= 825 days certificate life time but issued before 2018/03/01"
               fileout "cert_validityPeriod${json_postfix}" "INFO" "$((diffseconds / (3600 * 24) )) =< 825 days"
          fi
     else
          # All is fine with valididy period
          # We ignore for now certificates < 2018/03/01. On the screen we only show debug info
          [[ "$DEBUG" -ge 1 ]] && outln "${spaces}DEBUG: all is fine with total certificate life time"
          fileout "cert_validityPeriod${json_postfix}" "INFO" "No finding"
     fi

     certificates_provided=1+$(grep -c "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TEMPDIR/intermediatecerts.pem)
     out "$indent"; pr_bold " # of certificates provided"; out "   $certificates_provided"
     fileout "certs_countServer${json_postfix}" "INFO" "${certificates_provided}"
     if "$certificate_list_ordering_problem"; then
          prln_svrty_low " (certificate list ordering problem)"
          fileout "certs_list_ordering_problem${json_postfix}" "LOW" "yes"
     else
          fileout "certs_list_ordering_problem${json_postfix}" "INFO" "no"
          outln
     fi

     if "$PHONE_OUT"; then
          out "$indent"; pr_bold " In pwnedkeys.com DB          "
          check_pwnedkeys "$HOSTCERT" "$cert_key_algo" "$cert_keysize"
          case "$?" in
               0) outln "not checked"; fileout "pwnedkeys${json_postfix}" "INFO" "not checked" ;;
               1) outln "not in database"; fileout "pwnedkeys${json_postfix}" "INFO" "not in database" ;;
               2) pr_svrty_critical "NOT ok --"; outln " key appears in database"; fileout "pwnedkeys${json_postfix}" "CRITICAL" "private key is known" ;;
               7) prln_warning "error querying https://v1.pwnedkeys.com"; fileout "pwnedkeys${json_postfix}" "WARN" "connection error" ;;
          esac
     fi

     out "$indent"; pr_bold " Certificate Revocation List  "
     jsonID="cert_crlDistributionPoints"
     # ~ get next 50 lines after pattern , strip until Signature Algorithm and retrieve URIs
     crl="$(awk '/X509v3 CRL Distribution/{i=50} i&&i--' <<< "$cert_txt" | awk '/^$/,/^            [a-zA-Z0-9]+|^    Signature Algorithm:/' | awk -F'URI:' '/URI/ { print $2 }')"
     if [[ -z "$crl" ]] ; then
          fileout "${jsonID}${json_postfix}" "INFO" "--"
          outln "--"
     else
          if [[ $(count_lines "$crl") -eq 1 ]]; then
               out "$crl"
               if [[ "$expfinding" != "expired" ]]; then
                    check_revocation_crl "$crl" "cert_crlRevoked${json_postfix}"
                    ret=$((ret +$?))
               fi
               outln
          else # more than one CRL
               first_crl=true
               while read -r line; do
                    if "$first_crl"; then
                         first_crl=false
                    else
                         out "$spaces"
                    fi
                    out "$line"
                    if [[ "$expfinding" != expired ]]; then
                         check_revocation_crl "$line" "cert_crlRevoked${json_postfix}"
                         ret=$((ret +$?))
                    fi
                    outln
               done <<< "$crl"
          fi
          fileout "${jsonID}${json_postfix}" "INFO" "$crl"
     fi

     out "$indent"; pr_bold " OCSP URI                     "
     jsonID="cert_ocspURL"
     ocsp_uri=$($OPENSSL x509 -in $HOSTCERT -noout -ocsp_uri 2>>$ERRFILE)
     if [[ -z "$ocsp_uri" ]]; then
          outln "--"
          fileout "${jsonID}${json_postfix}" "INFO" "--"
     else
          if [[ $(count_lines "$ocsp_uri") -eq 1 ]]; then
               out "$ocsp_uri"
               if [[ "$expfinding" != "expired" ]]; then
                    check_revocation_ocsp "$ocsp_uri" "" "cert_ocspRevoked${json_postfix}"
               fi
               ret=$((ret +$?))
               outln
          else
               first_ocsp=true
               while read -r line; do
                    if "$first_ocsp"; then
                         first_ocsp=false
                    else
                         out "$spaces"
                    fi
                    out "$line"
                    if [[ "$expfinding" != "expired" ]]; then
                         check_revocation_ocsp "$line" "" "cert_ocspRevoked${json_postfix}"
                         ret=$((ret +$?))
                    fi
                    outln
               done <<< "$ocsp_uri"
          fi
          fileout "${jsonID}${json_postfix}" "INFO" "$ocsp_uri"
     fi
     if [[ -z "$ocsp_uri" ]] && [[ -z "$crl" ]]; then
          out "$spaces"
          pr_svrty_high "NOT ok --"
          outln " neither CRL nor OCSP URI provided"
          fileout "cert_revocation${json_postfix}" "HIGH" "Neither CRL nor OCSP URI provided"
     fi

     out "$indent"; pr_bold " OCSP stapling                "
     jsonID="OCSP stapling"
     if grep -a "OCSP response" <<< "$ocsp_response" | grep -q "no response sent" ; then
          if [[ -n "$ocsp_uri" ]]; then
               pr_svrty_low "not offered"
               fileout "${jsonID}${json_postfix}" "LOW" "OCSP stapling is not enabled.\nOCSP stapling can be used to enhance the OCSP protocol by letting the webhosting site be more proactive in improving the client (browsing) experience. OCSP stapling allows the certificate presenter (i.e. web server) to query the OCSP responder directly and then cache the response. This securely cached response is then delivered with the TLS/SSL handshake via the Certificate Status Request extension response, ensuring that the browser gets the same response performance for the certificate status as it does for the website content.\nOCSP stapling addresses a privacy concern with OCSP because the CA no longer receives the revocation requests directly from the client (browser). OCSP stapling also addresses concerns about OCSP SSL negotiation delays by removing the need for a separate network connection to a CA’s responders.\nRef: https://www.digicert.com/kb/enabling-ocsp-stapling.htm"
          else
               out "not offered"
               fileout "${jsonID}${json_postfix}" "INFO" "not offered"
          fi
     else
          if grep -a "OCSP Response Status" <<< "$ocsp_response_status" | grep -q successful; then
               pr_svrty_good "offered"
               fileout "${jsonID}${json_postfix}" "OK" "offered"
               provides_stapling=true
               check_revocation_ocsp "" "$ocsp_response_binary" "cert_ocspRevoked${json_postfix}"
          elif [[ "$ocsp_response" =~ Responder\ Error: ]]; then
               response="$(awk '/Responder Error:/ { print $3 }' <<< "$ocsp_response")"
               pr_warning "stapled OCSP response contained an error response from OCSP responder: $response"
               fileout "${jsonID}${json_postfix}" "WARN" "stapled OCSP response contained an error response from OCSP responder: $response"
          else
               if $GOST_STATUS_PROBLEM; then
                    pr_warning "(GOST servers make problems here, sorry)"
                    fileout "${jsonID}${json_postfix}" "WARN" "(The GOST server made a problem here, sorry)"
                    ((ret++))
               else
                    out "(response status unknown)"
                    fileout "${jsonID}${json_postfix}" "OK" " not sure what's going on here, '$ocsp_response'"
                    debugme grep -a -A20 -B2 "OCSP response"  <<<"$ocsp_response"
                    ((ret++))
               fi
          fi
     fi
     outln

     out "$indent"; pr_bold " OCSP must staple extension   ";
     must_staple "$json_postfix" "$provides_stapling" "$cert_txt"

     out "$indent"; pr_bold " DNS CAA RR"; out " (experimental)    "
     jsonID="DNS CAA record"
     caa_node="$NODE"
     caa=""
     while ( [[ -z "$caa" ]] &&  [[ ! -z "$caa_node" ]] ); do
          caa="$(get_caa_rr_record $caa_node)"
          [[ $caa_node =~ '.'$ ]] || caa_node+="."
          caa_node=${caa_node#*.}
     done
     if [[ -n "$caa" ]]; then
          pr_svrty_good "available"; out " - please check for match with \"Issuer\" above"
          if [[ $(count_lines "$caa") -eq 1 ]]; then
               out ": "
          else
               outln; out "$spaces"
          fi
          while read caa; do
               if [[ -n "$caa" ]]; then
                    all_caa+="$caa, "
               fi
          done <<< "$caa"
          all_caa=${all_caa%, }                 # strip trailing comma
          pr_italic "$(out_row_aligned_max_width "$all_caa" "$indent                              " $TERM_WIDTH)"
          fileout "${jsonID}${json_postfix}" "OK" "$all_caa"
     elif [[ -n "$NODNS" ]]; then
          out "(instructed to minimize DNS queries)"
          fileout "${jsonID}${json_postfix}" "INFO" "check skipped as instructed"
     else
          pr_svrty_low "not offered"
          fileout "${jsonID}${json_postfix}" "LOW" "Missing DNS CAA record.\nWhen a CAA record is not found, a malicious hacker can quickly generate a Certificate Signing Request (CSR) for your domain and have the certificate signed by any domain. This is a security threat that you should not allow to happen."
     fi
     outln

     out "$indent"; pr_bold " Certificate Transparency     ";
     jsonID="certificate_transparency"
     if [[ "$ct" =~ extension ]]; then
          pr_svrty_good "yes"; outln " ($ct)"
          fileout "${jsonID}${json_postfix}" "OK" "yes ($ct)"
     else
          outln "$ct"
          fileout "${jsonID}${json_postfix}" "INFO" "$ct"
     fi
     outln
     return $ret
}

run_server_defaults() {
     local ciph newhostcert sni
     local match_found
     local sessticket_lifetime_hint="" sessticket_proto="" lifetime unit
     local -i i n
     local -i certs_found=0
     local -i ret=0
     local -a previous_hostcert previous_hostcert_txt previous_hostcert_type
     local -a previous_hostcert_issuer previous_intermediates previous_ordering_problem keysize tested_cipher
     local -a ocsp_response_binary ocsp_response ocsp_response_status sni_used tls_version ct
     local -a ciphers_to_test certificate_type
     local -a -i success
     local cn_nosni cn_sni sans_nosni sans_sni san tls_extensions
     local using_sockets=true

     "$SSL_NATIVE" && using_sockets=false

     # Try each public key type once:
     # ciphers_to_test[1]: cipher suites using certificates with RSA signature public keys
     # ciphers_to_test[2]: cipher suites using certificates with RSA key encipherment public keys
     # ciphers_to_test[3]: cipher suites using certificates with DSA signature public keys
     # ciphers_to_test[4]: cipher suites using certificates with DH key agreement public keys
     # ciphers_to_test[5]: cipher suites using certificates with ECDH key agreement public keys
     # ciphers_to_test[6]: cipher suites using certificates with ECDSA signature public keys
     # ciphers_to_test[7]: cipher suites using certificates with GOST R 34.10 (either 2001 or 94) public keys
     ciphers_to_test[1]="aRSA:eRSA"
     ciphers_to_test[2]=""
     ciphers_to_test[3]="aDSS:aDH:aECDH:aECDSA:aGOST"
     ciphers_to_test[4]=""
     ciphers_to_test[5]=""
     ciphers_to_test[6]=""
     ciphers_to_test[7]=""
     ciphers_to_test[8]="tls1_3_RSA"
     ciphers_to_test[9]="tls1_3_ECDSA"
     certificate_type[1]="" ; certificate_type[2]=""
     certificate_type[3]=""; certificate_type[4]=""
     certificate_type[5]="" ; certificate_type[6]=""
     certificate_type[7]="" ; certificate_type[8]="RSASig"
     certificate_type[9]="ECDSA"

     for (( n=1; n <= 16 ; 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 -ne 1 ]] && [[ "$OPTIMAL_PROTO" == -ssl2 ]]; then
               ciphers_to_test[n]=""
          elif [[ $n -ge 10 ]]; then
               ciphers_to_test[n]=""
               [[ ${success[n-9]} -eq 0 ]] && [[ $(has_server_protocol "tls1_1") -ne 1 ]] && \
                    ciphers_to_test[n]="${ciphers_to_test[n-9]}" && certificate_type[n]="${certificate_type[n-9]}"
          fi

          if [[ -n "${ciphers_to_test[n]}" ]]; then
               if [[ $n -ge 10 ]]; then
                    sni="$SNI"
                    SNI=""
                    get_server_certificate "${ciphers_to_test[n]}" "tls1_1"
                    success[n]=$?
                    SNI="$sni"
               else
                    get_server_certificate "${ciphers_to_test[n]}"
                    success[n]=$?
               fi
               if [[ ${success[n]} -eq 0 ]] && [[ -s "$HOSTCERT" ]]; then
                    [[ $n -ge 10 ]] && [[ ! -e $HOSTCERT.nosni ]] && cp $HOSTCERT $HOSTCERT.nosni
                    cp "$TEMPDIR/$NODEIP.get_server_certificate.txt" $TMPFILE
                    >$ERRFILE
                    if [[ -z "$sessticket_lifetime_hint" ]]; then
                         sessticket_lifetime_hint=$(awk '/session ticket life/ { if (!found) print; found=1 }' $TMPFILE)
                         sessticket_proto="$(get_protocol "$TMPFILE")"
                    fi

                    if [[ $n -le 7 ]]; then
                         ciph="$(get_cipher $TMPFILE)"
                         if [[ "$ciph" != TLS_* ]] && [[ "$ciph" != SSL_* ]]; then
                              ciph="$(openssl2rfc "$ciph")"
                         fi
                         if [[ "$ciph" == TLS_DHE_RSA_* ]] || [[ "$ciph" == TLS_ECDHE_RSA_* ]] || [[ "$ciph" == TLS_CECPQ1_RSA_* ]]; then
                              certificate_type[n]="RSASig"
                              if [[ -z "${ciphers_to_test[n+1]}" ]]; then
                                   ciphers_to_test[n+1]="${ciphers_to_test[n]/aRSA/}"
                                   ciphers_to_test[n+1]="${ciphers_to_test[n+1]/::/:}"
                                   [[ "${ciphers_to_test[n+1]:0:1}" == : ]] && ciphers_to_test[n+1]="${ciphers_to_test[n+1]:1}"
                              fi
                              ciphers_to_test[n]="aRSA"
                         elif [[ "$ciph" == TLS_RSA_* ]] || [[ "$ciph" == SSL_* ]] || [[ "$ciph" == TLS_GOST*_RSA_* ]]; then
                              certificate_type[n]="RSAKMK"
                              if [[ -z "${ciphers_to_test[n+1]}" ]]; then
                                   ciphers_to_test[n+1]="${ciphers_to_test[n]/eRSA/}"
                                   ciphers_to_test[n+1]="${ciphers_to_test[n+1]/::/:}"
                                   [[ "${ciphers_to_test[n+1]:0:1}" == : ]] && ciphers_to_test[n+1]="${ciphers_to_test[n+1]:1}"
                              fi
                              ciphers_to_test[n]="eRSA"
                         elif [[ "$ciph" == TLS_DHE_DSS_* ]]; then
                              certificate_type[n]="DSA"
                              if [[ -z "${ciphers_to_test[n+1]}" ]]; then
                                   ciphers_to_test[n+1]="${ciphers_to_test[n]/aDSS/}"
                                   ciphers_to_test[n+1]="${ciphers_to_test[n+1]/::/:}"
                                   [[ "${ciphers_to_test[n+1]:0:1}" == : ]] && ciphers_to_test[n+1]="${ciphers_to_test[n+1]:1}"
                              fi
                              ciphers_to_test[n]="aDSS"
                         elif [[ "$ciph" == TLS_DH_* ]]; then
                              certificate_type[n]="DH"
                              if [[ -z "${ciphers_to_test[n+1]}" ]]; then
                                   ciphers_to_test[n+1]="${ciphers_to_test[n]/aDH/}"
                                   ciphers_to_test[n+1]="${ciphers_to_test[n+1]/::/:}"
                                   [[ "${ciphers_to_test[n+1]:0:1}" == : ]] && ciphers_to_test[n+1]="${ciphers_to_test[n+1]:1}"
                              fi
                              ciphers_to_test[n]="aDH"
                         elif [[ "$ciph" == TLS_ECDH_* ]]; then
                              certificate_type[n]="ECDH"
                              if [[ -z "${ciphers_to_test[n+1]}" ]]; then
                                   ciphers_to_test[n+1]="${ciphers_to_test[n]/aECDH/}"
                                   ciphers_to_test[n+1]="${ciphers_to_test[n+1]/::/:}"
                                   [[ "${ciphers_to_test[n+1]:0:1}" == : ]] && ciphers_to_test[n+1]="${ciphers_to_test[n+1]:1}"
                              fi
                              ciphers_to_test[n]="aECDH"
                         elif [[ "$ciph" == TLS_ECDHE_ECDSA_* ]] || [[ "$ciph" == TLS_CECPQ1_ECDSA_* ]]; then
                              certificate_type[n]="ECDSA"
                              if [[ -z "${ciphers_to_test[n+1]}" ]]; then
                                   ciphers_to_test[n+1]="${ciphers_to_test[n]/aECDSA/}"
                                   ciphers_to_test[n+1]="${ciphers_to_test[n+1]/::/:}"
                                   [[ "${ciphers_to_test[n+1]:0:1}" == : ]] && ciphers_to_test[n+1]="${ciphers_to_test[n+1]:1}"
                              fi
                              ciphers_to_test[n]="aECDSA"
                         elif [[ "$ciph" == TLS_GOST* ]]; then
                              certificate_type[n]="GOST"
                              if [[ -z "${ciphers_to_test[n+1]}" ]]; then
                                   ciphers_to_test[n+1]="${ciphers_to_test[n]/aGOST/}"
                                   ciphers_to_test[n+1]="${ciphers_to_test[n+1]/::/:}"
                                   [[ "${ciphers_to_test[n+1]:0:1}" == : ]] && ciphers_to_test[n+1]="${ciphers_to_test[n+1]:1}"
                              fi
                              ciphers_to_test[n]="aGOST"
                         fi
                    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 10 ]] && [[ $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 "$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 "$(get_san_dns_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 "$(get_san_dns_from_cert "$HOSTCERT")")"
                                   if [[ "$sans_nosni" == "$sans_sni" ]]; then
                                        success[n]=0
                                   else
                                        while read -r san; do
                                             [[ -n "$san" ]] && [[ " $sans_sni " =~ " $san " ]] && success[n]=0 && break
                                        done <<< "$sans_nosni"
                                   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))
                         tested_cipher[certs_found]=${ciphers_to_test[n]}
                         keysize[certs_found]=$(awk '/Server public key/ { print $(NF-1) }' $TMPFILE)
                         # If an OCSP response was sent, then get the full
                         # response so that certificate_info() can determine
                         # whether it includes a certificate transparency extension.
                         ocsp_response_binary[certs_found]="$STAPLED_OCSP_RESPONSE"
                         if grep -a "OCSP response:" $TMPFILE | grep -q "no response sent"; then
                              ocsp_response[certs_found]="$(grep -a "OCSP response" $TMPFILE)"
                         else
                              ocsp_response[certs_found]="$(awk -v n=2 '/OCSP response:/ {start=1; inc=2} /======================================/ { if (start) {inc--} } inc' $TMPFILE)"
                         fi
                         ocsp_response_status[certs_found]=$(grep -a "OCSP Response Status" $TMPFILE)
                         previous_hostcert[certs_found]=$newhostcert
                         previous_hostcert_txt[certs_found]="$($OPENSSL x509 -noout -text 2>>$ERRFILE <<< "$newhostcert")"
                         previous_intermediates[certs_found]=$(cat $TEMPDIR/intermediatecerts.pem)
                         previous_hostcert_issuer[certs_found]=""
                         [[ -n "${previous_intermediates[certs_found]}" ]] && [[ -r $TEMPDIR/hostcert_issuer.pem ]] && \
                              previous_hostcert_issuer[certs_found]=$(cat $TEMPDIR/hostcert_issuer.pem)
                         previous_ordering_problem[certs_found]=$CERTIFICATE_LIST_ORDERING_PROBLEM
                         [[ $n -ge 10 ]] && sni_used[certs_found]="" || sni_used[certs_found]="$SNI"
                         tls_version[certs_found]="$DETECTED_TLS_VERSION"
                         previous_hostcert_type[certs_found]=" ${certificate_type[n]}"
                         if [[ $DEBUG -ge 1 ]]; then
                              echo "${previous_hostcert[certs_found]}" > $TEMPDIR/host_certificate_$certs_found.pem
                              echo "${previous_hostcert_txt[certs_found]}" > $TEMPDIR/host_certificate_$certs_found.txt
                         fi
                    else
                         previous_hostcert_type[i]+=" ${certificate_type[n]}"
                    fi
               fi
          fi
     done

     determine_tls_extensions

     if [[ $? -eq 0 ]] && [[ "$OPTIMAL_PROTO" != -ssl2 ]]; then
          cp "$TEMPDIR/$NODEIP.determine_tls_extensions.txt" $TMPFILE
          >$ERRFILE
          if [[ -z "$sessticket_lifetime_hint" ]]; then
               sessticket_lifetime_hint=$(awk '/session ticket lifetime/ { if (!found) print; found=1 }' $TMPFILE)
               sessticket_proto="$(get_protocol "$TMPFILE")"
          fi
     fi
     if "$using_sockets" && ! "$TLS13_ONLY" && [[ -z "$sessticket_lifetime_hint" ]] && [[ "$OPTIMAL_PROTO" != -ssl2 ]]; then
          if "$HAS_TLS13" && ( [[ -z "$OPTIMAL_PROTO" ]] || [[ "$OPTIMAL_PROTO" == -tls1_3 ]] ) ; then
               # If a session ticket were sent in response to a TLSv1.3 ClientHello, then a session ticket
               # would have been found by get_server_certificate(). So, try again with a TLSv1.2 ClientHello.
               $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -no_tls1_3 -connect $NODEIP:$PORT $PROXY $SNI") </dev/null 2>$ERRFILE >$TMPFILE
          else
               $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS "$OPTIMAL_PROTO" -connect $NODEIP:$PORT $PROXY $SNI") </dev/null 2>$ERRFILE >$TMPFILE
          fi
          if sclient_connect_successful $? $TMPFILE; then
               sessticket_lifetime_hint=$(awk '/session ticket lifetime/ { if (!found) print; found=1 }' $TMPFILE)
               sessticket_proto="$(get_protocol "$TMPFILE")"
          fi
     fi
     [[ -z "$sessticket_lifetime_hint" ]] && TLS_TICKETS=false || TLS_TICKETS=true

     debugme echo "# certificates found $certs_found"
     # Now that all of the server's certificates have been found, determine for
     # each certificate whether certificate transparency information is provided.
     for (( i=1; i <= certs_found; i++ )); do
          ct[i]="$(certificate_transparency "${previous_hostcert_txt[i]}" "${ocsp_response[i]}" "$certs_found" "${tested_cipher[i]}" "${sni_used[i]}" "${tls_version[i]}")"
          # If certificate_transparency() called tls_sockets() and found a "signed certificate timestamps" extension,
          # then add it to $TLS_EXTENSIONS, since it may not have been found by determine_tls_extensions().
          [[ $certs_found -gt 1 ]] && [[ "${ct[i]}" == TLS\ extension ]] && extract_new_tls_extensions "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt"
     done

     outln
     pr_headlineln " Testing server defaults (Server Hello) "
     outln

     pr_bold " TLS extensions (standard)    "
     if [[ -z "$TLS_EXTENSIONS" ]]; then
          outln "(none)"
          fileout "TLS_extensions" "INFO" "(none)"
     else
#FIXME: we rather want to have the chance to print each ext in italics or another format.
# Atm is a string of quoted strings -- that needs to be fixed at the root then
          # 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_EXTENSIONS"
     fi

     pr_bold " Session Ticket RFC 5077 hint "
     jsonID="TLS_session_ticket"
     if [[ -z "$sessticket_lifetime_hint" ]]; then
          outln "no -- no lifetime advertised"
          fileout "${jsonID}" "INFO" "no -- no lifetime advertised"
          # it MAY be given a hint of the lifetime of the ticket, see https://tools.ietf.org/html/rfc5077#section-5.6 .
          # Sometimes it just does not -- but it then may also support TLS session tickets reuse
     else
          lifetime=$(grep -a lifetime <<< "$sessticket_lifetime_hint" | sed 's/[A-Za-z:() ]//g')
          unit=$(grep -a lifetime <<< "$sessticket_lifetime_hint" | sed -e 's/^.*'"$lifetime"'//' -e 's/[ ()]//g')
          out "$lifetime $unit"
          if [[ $((3600 * 24)) -lt $lifetime ]]; then
               prln_svrty_low " but: FS requires session ticket keys to be rotated < daily !"
               fileout "$jsonID" "LOW" "valid for $lifetime $unit (>daily)"
          else
               outln ", session tickets keys seems to be rotated < daily"
               fileout "$jsonID" "INFO" "valid for $lifetime $unit only (<daily)"
          fi
     fi

     pr_bold " SSL Session ID support       "
     jsonID="SSL_sessionID_support"
     if "$NO_SSL_SESSIONID"; then
          outln "no"
          fileout "$jsonID" "INFO" "no"
     else
          outln "yes"
          fileout "$jsonID" "INFO" "yes"
     fi

     pr_bold " Session Resumption           "
     jsonID="sessionresumption_ticket"
     sub_session_resumption "$sessticket_proto"
     case $? in
          0) SESS_RESUMPTION[2]="ticket=yes"
             out "Tickets: yes, "
             fileout "$jsonID" "INFO" "supported"
          ;;
          1) SESS_RESUMPTION[2]="ticket=no"
             out "Tickets no, "
             fileout "$jsonID" "INFO" "not supported"
             ;;
          5) SESS_RESUMPTION[2]="ticket=noclue"
             pr_warning "Ticket resumption test failed, pls report / "
             fileout "$jsonID" "WARN" "check failed, pls report"
             ((ret++))
             ;;
          6) SESS_RESUMPTION[2]="ticket=clientauth"
             pr_warning "Client Auth: Ticket resumption test not supported / "
             fileout "$jsonID" "WARN" "check couldn't be performed because of client authentication"
             ;;
          7) SESS_RESUMPTION[2]="ticket=unsuccessful"
             pr_warning "Connect problem: Ticket resumption test not possible / "
             fileout "$jsonID" "WARN" "check failed because of connect problem"
             ((ret++))
             ;;
     esac

     jsonID="sessionresumption_ID"
     if "$NO_SSL_SESSIONID"; then
          SESS_RESUMPTION[1]="ID=no"
          outln "ID: no"
          fileout "$jsonID" "INFO" "No Session ID, no resumption"
     else
          sub_session_resumption "$sessticket_proto" ID
          case $? in
               0) SESS_RESUMPTION[1]="ID=yes"
                  outln "ID: yes"
                  fileout "$jsonID" "INFO" "supported"
                  ;;
               1|2) SESS_RESUMPTION[1]="ID=no"
                  outln "ID: no"
                  fileout "$jsonID" "INFO" "not supported"
                  ;;
               5) SESS_RESUMPTION[1]="ID=noclue"
                  prln_warning "ID resumption test failed, pls report"
                  fileout "$jsonID" "WARN" "check failed, pls report"
                  ((ret++))
                  ;;
               6) SESS_RESUMPTION[1]="ID=clientauth"
                  # [[ ${SESS_RESUMPTION[2]} =~ clientauth ]] || pr_warning "Client Auth: "
                  prln_warning "Client Auth: ID resumption test not supported"
                  fileout "$jsonID" "WARN" "check couldn't be performed because of client authentication"
                  ;;
               7) SESS_RESUMPTION[1]="ID=unsuccessful"
                  prln_warning "ID resumption test failed"
                  fileout "$jsonID" "WARN" "check failed because of connect problem"
                  ((ret++))
                  ;;
          esac
     fi

     tls_time

     if [[ -n "$SNI" ]] && [[ $certs_found -ne 0 ]] && [[ ! -e $HOSTCERT.nosni ]]; then
          # no cipher suites specified here. We just want the default vhost subject
          if ! "$HAS_TLS13" && [[ $(has_server_protocol "tls1_3") -eq 0 ]]; then
               sni="$SNI" ; SNI=""
               mv $HOSTCERT $HOSTCERT.save
               # Send same list of cipher suites as OpenSSL 1.1.1 sends (but with
               # all 5 TLSv1.3 ciphers offered.
               tls_sockets "04" \
                           "c0,2c, c0,30, 00,9f, cc,a9, cc,a8, cc,aa, c0,2b, c0,2f,
                            00,9e, c0,24, c0,28, 00,6b, c0,23, c0,27, 00,67, c0,0a,
                            c0,14, 00,39, c0,09, c0,13, 00,33, 00,9d, 00,9c, 13,02,
                            13,03, 13,01, 13,04, 13,05, 00,3d, 00,3c, 00,35, 00,2f,
                            00,ff" \
                            "all+"
               success[0]=$?
               if [[ ${success[0]} -eq 0 ]] || [[ ${success[0]} -eq 2 ]]; then
                    if [[ -s $HOSTCERT ]]; then
                         mv $HOSTCERT $HOSTCERT.nosni
                    else
                         # The connection was successful, but the certificate could
                         # not be obtained (probably because the connection was TLS 1.3
                         # and $OPENSSL does not support the key exchange group that was
                         # selected). So, try again using OpenSSL (which will not use a TLS 1.3
                         # ClientHello).
                         $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $OPTIMAL_PROTO") 2>>$ERRFILE </dev/null | \
                              awk '/-----BEGIN/,/-----END/ { print $0 }'  >$HOSTCERT.nosni
                    fi
               else
                    >$HOSTCERT.nosni
               fi
               mv $HOSTCERT.save $HOSTCERT
               SNI="$sni"
          else
               $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $OPTIMAL_PROTO") 2>>$ERRFILE </dev/null | \
                    awk '/-----BEGIN/,/-----END/ { print $0 }'  >$HOSTCERT.nosni
          fi
     elif [[ $certs_found -eq 0 ]] && [[ -s "$HOSTCERT" ]]; then
          outln
          generic_nonfatal "Client problem, shouldn't happen: Host certificate found but we can't continue with \"server defaults\"."
     elif [[ $certs_found -eq 0 ]]; then
          outln
          if $TLS13_ONLY; then
               generic_nonfatal "Client problem: We need openssl supporting TLS 1.3. We can't continue with \"server defaults\" as we cannot retrieve the certificate. "
          else
               generic_nonfatal "Client problem, No server cerificate could be retrieved. Thus we can't continue with \"server defaults\"."
          fi
     fi
     [[ $DEBUG -ge 1 ]] && [[ -e $HOSTCERT.nosni ]] && $OPENSSL x509 -in $HOSTCERT.nosni -text -noout 2>>$ERRFILE > $HOSTCERT.nosni.txt

     fileout "cert_numbers" "INFO" "$certs_found"
     for (( i=1; i <= certs_found; i++ )); do
          echo "${previous_hostcert[i]}" > $HOSTCERT
          echo "${previous_intermediates[i]}" > $TEMPDIR/intermediatecerts.pem
          echo "${previous_hostcert_issuer[i]}" > $TEMPDIR/hostcert_issuer.pem
          certificate_info "$i" "$certs_found" "${previous_hostcert_txt[i]}" \
               "${tested_cipher[i]}" "${keysize[i]}" "${previous_hostcert_type[i]}" \
               "${ocsp_response_binary[i]}" "${ocsp_response[i]}" \
               "${ocsp_response_status[i]}" "${sni_used[i]}" "${ct[i]}" \
               "${previous_ordering_problem[i]}"
               [[ $? -ne 0 ]] && ((ret++))
     done
     return $ret
}

get_session_ticket_lifetime_from_serverhello() {
     awk '/session ticket.*lifetime/ { print $(NF-1) "$1" }'
}

get_san_dns_from_cert() {
     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')"
}


run_fs() {
     local -i sclient_success
     local fs_offered=false ecdhe_offered=false ffdhe_offered=false
     local fs_tls13_offered=false
     local protos_to_try proto hexc dash fs_cipher sslvers auth mac export curve dhlen
     local -a hexcode normalized_hexcode ciph rfc_ciph kx enc ciphers_found sigalg ossl_supported
     # generated from 'kEECDH:kEDH:!aNULL:!eNULL:!DES:!3DES:!RC4' with openssl 1.0.2i and openssl 1.1.0
     local fs_cipher_list="DHE-DSS-AES128-GCM-SHA256:DHE-DSS-AES128-SHA256:DHE-DSS-AES128-SHA:DHE-DSS-AES256-GCM-SHA384:DHE-DSS-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-DSS-CAMELLIA128-SHA256:DHE-DSS-CAMELLIA128-SHA:DHE-DSS-CAMELLIA256-SHA256:DHE-DSS-CAMELLIA256-SHA:DHE-DSS-SEED-SHA:DHE-RSA-AES128-CCM8:DHE-RSA-AES128-CCM:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-CCM8:DHE-RSA-AES256-CCM:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-CAMELLIA128-SHA256:DHE-RSA-CAMELLIA128-SHA:DHE-RSA-CAMELLIA256-SHA256:DHE-RSA-CAMELLIA256-SHA:DHE-RSA-CHACHA20-POLY1305-OLD:DHE-RSA-CHACHA20-POLY1305:DHE-RSA-SEED-SHA:ECDHE-ECDSA-AES128-CCM8:ECDHE-ECDSA-AES128-CCM:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-CCM8:ECDHE-ECDSA-AES256-CCM:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-CAMELLIA128-SHA256:ECDHE-ECDSA-CAMELLIA256-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305-OLD:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-RSA-CAMELLIA128-SHA256:ECDHE-RSA-CAMELLIA256-SHA384:ECDHE-RSA-CHACHA20-POLY1305-OLD:ECDHE-RSA-CHACHA20-POLY1305"
     local fs_hex_cipher_list="" ciphers_to_test tls13_ciphers_to_test
     local ecdhe_cipher_list="" tls13_cipher_list="" ecdhe_cipher_list_hex="" ffdhe_cipher_list_hex=""
     local curves_hex=("00,01" "00,02" "00,03" "00,04" "00,05" "00,06" "00,07" "00,08" "00,09" "00,0a" "00,0b" "00,0c" "00,0d" "00,0e" "00,0f" "00,10" "00,11" "00,12" "00,13" "00,14" "00,15" "00,16" "00,17" "00,18" "00,19" "00,1a" "00,1b" "00,1c" "00,1d" "00,1e")
     local -a curves_ossl=("sect163k1" "sect163r1" "sect163r2" "sect193r1" "sect193r2" "sect233k1" "sect233r1" "sect239k1" "sect283k1" "sect283r1" "sect409k1" "sect409r1" "sect571k1" "sect571r1" "secp160k1" "secp160r1" "secp160r2" "secp192k1" "prime192v1" "secp224k1" "secp224r1" "secp256k1" "prime256v1" "secp384r1" "secp521r1" "brainpoolP256r1" "brainpoolP384r1" "brainpoolP512r1" "X25519" "X448")
     local -a curves_ossl_output=("K-163" "sect163r1" "B-163" "sect193r1" "sect193r2" "K-233" "B-233" "sect239k1" "K-283" "B-283" "K-409" "B-409" "K-571" "B-571" "secp160k1" "secp160r1" "secp160r2" "secp192k1" "P-192" "secp224k1" "P-224" "secp256k1" "P-256" "P-384" "P-521" "brainpoolP256r1" "brainpoolP384r1" "brainpoolP512r1" "X25519" "X448")
     local -ai curves_bits=(163 162 163 193 193 232 233 238 281 282 407 409 570 570 161 161 161 192 192 225 224 256 256 384 521 256 384 512 253 448)
     # Many curves have been deprecated, and RFC 8446, Appendix B.3.1.4, states
     # that these curves MUST NOT be offered in a TLS 1.3 ClientHello.
     local -a curves_deprecated=("true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "true" "false" "false" "false" "true" "true" "true" "false" "false")
     local -a ffdhe_groups_hex=("01,00" "01,01" "01,02" "01,03" "01,04")
     local -a ffdhe_groups_output=("ffdhe2048" "ffdhe3072" "ffdhe4096" "ffdhe6144" "ffdhe8192")
     local -a supported_curve
     local -i nr_supported_ciphers=0 nr_curves=0 nr_ossl_curves=0 i j low high
     local fs_ciphers curves_offered="" curves_to_test temp
     local len1 len2 curve_found
     local key_bitstring quality_str
     local -i len_dh_p quality
     local has_dh_bits="$HAS_DH_BITS"
     local using_sockets=true
     local jsonID="FS"

     "$SSL_NATIVE" && using_sockets=false
     "$FAST" && using_sockets=false
     [[ $TLS_NR_CIPHERS == 0 ]] && using_sockets=false

     outln
     pr_headline " Testing robust forward secrecy (FS)"; prln_underline " -- omitting Null Authentication/Encryption, 3DES, RC4 "
     if ! "$using_sockets"; then
          [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && pr_warning " Cipher mapping not available, doing a fallback to openssl"
          if ! "$HAS_DH_BITS" && "$WIDE"; then
               [[ $TLS_NR_CIPHERS == 0 ]] && ! "$SSL_NATIVE" && ! "$FAST" && out "."
               pr_warning "    (Your $OPENSSL cannot show DH/ECDH bits)"
          fi
          outln
     fi

     if "$using_sockets" || [[ $OSSL_VER_MAJOR -lt 1 ]]; then
          for (( i=0; i < TLS_NR_CIPHERS; i++ )); do
               fs_cipher="${TLS_CIPHER_RFC_NAME[i]}"
               hexc="${TLS_CIPHER_HEXCODE[i]}"
               if ( [[ "$fs_cipher" == "TLS_DHE_"* ]] || [[ "$fs_cipher" == "TLS_ECDHE_"* ]] || [[ "${hexc:2:2}" == "13" ]] ) && \
                  [[ ! "$fs_cipher" =~ NULL ]] && [[ ! "$fs_cipher" =~ DES ]] && [[ ! "$fs_cipher" =~ RC4 ]] && \
                  [[ ! "$fs_cipher" =~ PSK ]] && ( "$using_sockets" || "${TLS_CIPHER_OSSL_SUPPORTED[i]}" ); then
                    fs_hex_cipher_list+=", ${hexc:2:2},${hexc:7:2}"
                    ciph[nr_supported_ciphers]="${TLS_CIPHER_OSSL_NAME[i]}"
                    rfc_ciph[nr_supported_ciphers]="${TLS_CIPHER_RFC_NAME[i]}"
                    kx[nr_supported_ciphers]="${TLS_CIPHER_KX[i]}"
                    enc[nr_supported_ciphers]="${TLS_CIPHER_ENC[i]}"
                    ciphers_found[nr_supported_ciphers]=false
                    sigalg[nr_supported_ciphers]=""
                    ossl_supported[nr_supported_ciphers]="${TLS_CIPHER_OSSL_SUPPORTED[i]}"
                    hexcode[nr_supported_ciphers]="${hexc:2:2},${hexc:7:2}"
                    if [[ "${hexc:2:2}" == 00 ]]; then
                         normalized_hexcode[nr_supported_ciphers]="x${hexc:7:2}"
                    else
                         normalized_hexcode[nr_supported_ciphers]="x${hexc:2:2}${hexc:7:2}"
                    fi
                    "$using_sockets" && ! "$has_dh_bits" && "$WIDE" && ossl_supported[nr_supported_ciphers]=false
                    nr_supported_ciphers+=1
               fi
          done
     else
          while read -r hexc dash ciph[nr_supported_ciphers] sslvers kx[nr_supported_ciphers] auth enc[nr_supported_ciphers] mac export; do
               ciphers_found[nr_supported_ciphers]=false
               if [[ "${hexc:2:2}" == 00 ]]; then
                    normalized_hexcode[nr_supported_ciphers]="x${hexc:7:2}"
               else
                    normalized_hexcode[nr_supported_ciphers]="x${hexc:2:2}${hexc:7:2}"
               fi
               sigalg[nr_supported_ciphers]=""
               ossl_supported[nr_supported_ciphers]=true
               nr_supported_ciphers+=1
          done < <(actually_supported_osslciphers "$fs_cipher_list" "ALL" "-V")
     fi

     if [[ $(has_server_protocol "tls1_3") -eq 0 ]]; then
          # All TLSv1.3 cipher suites offer robust FS.
          sclient_success=0
     elif "$using_sockets"; then
          tls_sockets "04" "${fs_hex_cipher_list:2}, 00,ff"
          sclient_success=$?
          [[ $sclient_success -eq 2 ]] && sclient_success=0
     else
          debugme echo $nr_supported_ciphers
          debugme echo $(actually_supported_osslciphers $fs_cipher_list "ALL")
          if [[ "$nr_supported_ciphers" -le "$CLIENT_MIN_FS" ]]; then
               outln
               prln_local_problem "You only have $nr_supported_ciphers FS ciphers on the client side "
               fileout "$jsonID" "WARN" "tests skipped as you only have $nr_supported_ciphers FS ciphers on the client site. ($CLIENT_MIN_FS are required)"
               return 1
          fi
          $OPENSSL s_client $(s_client_options "-cipher $fs_cipher_list -ciphersuites "ALL" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null
          sclient_connect_successful $? $TMPFILE
          sclient_success=$?
          [[ $sclient_success -eq 0 ]] && [[ $(grep -ac "BEGIN CERTIFICATE" $TMPFILE) -eq 0 ]] && sclient_success=1
     fi

     if [[ $sclient_success -ne 0 ]]; then
          outln
          prln_svrty_medium " No ciphers supporting Forward Secrecy (FS) offered"
          fileout "$jsonID" "MEDIUM" "No ciphers supporting Forward Secrecy offered"
          set_grade_cap "B" "Forward Secrecy (FS) is not supported"
     else
          outln
          fs_offered=true
          fs_ciphers=""
          pr_svrty_good " FS is offered (OK) "
          fileout "$jsonID" "OK" "offered"
          if "$WIDE"; then
               outln ", ciphers follow (client/browser support is important here) \n"
               neat_header
          else
               out "          "
          fi
          if "$HAS_TLS13"; then
               protos_to_try="-no_ssl2 -no_tls1_3"
          else
               protos_to_try="-no_ssl2"
          fi

          for proto in $protos_to_try; do
               while true; do
                    ciphers_to_test=""
                    tls13_ciphers_to_test=""
                    for (( i=0; i < nr_supported_ciphers; i++ )); do
                         if ! "${ciphers_found[i]}" && "${ossl_supported[i]}"; then
                              if [[ "${ciph[i]}" == TLS13* ]] || [[ "${ciph[i]}" == TLS_* ]] || [[ "${ciph[i]}" == AEAD-* ]]; then
                                   tls13_ciphers_to_test+=":${ciph[i]}"
                              else
                                   ciphers_to_test+=":${ciph[i]}"
                              fi
                         fi
                    done
                    [[ -z "$ciphers_to_test" ]] && [[ -z "$tls13_ciphers_to_test" ]] && break
                    $OPENSSL s_client $(s_client_options "$proto -cipher "\'${ciphers_to_test:1}\'" -ciphersuites "\'${tls13_ciphers_to_test:1}\'" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") &>$TMPFILE </dev/null
                    sclient_connect_successful $? $TMPFILE || break
                    fs_cipher=$(get_cipher $TMPFILE)
                    [[ -z "$fs_cipher" ]] && break
                    for (( i=0; i < nr_supported_ciphers; i++ )); do
                         [[ "$fs_cipher" == "${ciph[i]}" ]] && break
                    done
                    [[ $i -eq $nr_supported_ciphers ]] && break
                    ciphers_found[i]=true
                    if [[ "$fs_cipher" == TLS13* ]] || [[ "$fs_cipher" == TLS_* ]] || [[ "$fs_cipher" == AEAD-* ]]; then
                         fs_tls13_offered=true
                         "$WIDE" && kx[i]="$(read_dhtype_from_file $TMPFILE)"
                    fi
                    if "$WIDE"; then
                         dhlen=$(read_dhbits_from_file "$TMPFILE" quiet)
                         kx[i]="${kx[i]} $dhlen"
                    fi
                    "$WIDE" && "$SHOW_SIGALGO" && grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TMPFILE && \
                         sigalg[i]="$(read_sigalg_from_file "$TMPFILE")"
		    break
               done
          done
          if "$using_sockets"; then
               for proto in 04 03; do
                    while true; do
                         ciphers_to_test=""
                         for (( i=0; i < nr_supported_ciphers; i++ )); do
                              ! "${ciphers_found[i]}" && ciphers_to_test+=", ${hexcode[i]}"
                         done
                         [[ -z "$ciphers_to_test" ]] && break
                         [[ "$proto" == "04" ]] && [[ ! "$ciphers_to_test" =~ ,\ 13,[0-9a-f][0-9a-f] ]] && break
                         ciphers_to_test="$(strip_inconsistent_ciphers "$proto" "$ciphers_to_test")"
                         [[ -z "$ciphers_to_test" ]] && break
                         if "$WIDE" && "$SHOW_SIGALGO"; then
                              tls_sockets "$proto" "${ciphers_to_test:2}, 00,ff" "all"
                         else
                              tls_sockets "$proto" "${ciphers_to_test:2}, 00,ff" "ephemeralkey"
                         fi
                         sclient_success=$?
                         [[ $sclient_success -ne 0 ]] && [[ $sclient_success -ne 2 ]] && break
                         fs_cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")
                         for (( i=0; i < nr_supported_ciphers; i++ )); do
                              [[ "$fs_cipher" == "${rfc_ciph[i]}" ]] && break
                         done
                         [[ $i -eq $nr_supported_ciphers ]] && break
                         ciphers_found[i]=true
                         if [[ "${kx[i]}" == Kx=any ]]; then
                              fs_tls13_offered=true
                              "$WIDE" && kx[i]="$(read_dhtype_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")"
                         fi
                         if "$WIDE"; then
                              dhlen=$(read_dhbits_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" quiet)
                              kx[i]="${kx[i]} $dhlen"
                         fi
                         "$WIDE" && "$SHOW_SIGALGO" && [[ -r "$HOSTCERT" ]] && \
                              sigalg[i]="$(read_sigalg_from_file "$HOSTCERT")"
                    done
               done
          fi
          for (( i=0; i < nr_supported_ciphers; i++ )); do
               ! "${ciphers_found[i]}" && ! "$SHOW_EACH_C" && continue
               if "${ciphers_found[i]}"; then
                    if ( [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]] && [[ "${ciph[i]}" != "-" ]] ) || [[ "${rfc_ciph[i]}" == "-" ]]; then
                         fs_cipher="${ciph[i]}"
                    else
                         fs_cipher="${rfc_ciph[i]}"
                    fi
                    fs_ciphers+="$fs_cipher "

                    if [[ "${ciph[i]}" == ECDHE-* ]] || [[ "${ciph[i]}" == TLS13* ]] || [[ "${ciph[i]}" == TLS_* ]] || \
                       [[ "${ciph[i]}" == AEAD-* ]] || ( "$using_sockets" && [[ "${rfc_ciph[i]}" == TLS_ECDHE_* ]] ); then
                         ecdhe_offered=true
                         ecdhe_cipher_list_hex+=", ${hexcode[i]}"
                         if [[ "${ciph[i]}" != "-" ]]; then
                              if  [[ "${ciph[i]}" == TLS13* ]] || [[ "${ciph[i]}" == TLS_* ]] || [[ "${ciph[i]}" == AEAD-* ]]; then
                                   tls13_cipher_list+=":$fs_cipher"
                              else
                                   ecdhe_cipher_list+=":$fs_cipher"
                              fi
                         fi
                    fi
                    if [[ "${ciph[i]}" == "DHE-"* ]] || ( "$using_sockets" && [[ "${rfc_ciph[i]}" == "TLS_DHE_"* ]] ); then
                         ffdhe_offered=true
                         ffdhe_cipher_list_hex+=", ${hexcode[i]}"
                    elif [[ "${ciph[i]}" == TLS13* ]] || [[ "${ciph[i]}" == TLS_* ]] || [[ "${ciph[i]}" == AEAD-* ]]; then
                         ffdhe_cipher_list_hex+=", ${hexcode[i]}"
                    fi
               fi
               if "$WIDE"; then
                    neat_list "$(tolower "${normalized_hexcode[i]}")" "${ciph[i]}" "${kx[i]}" "${enc[i]}" "" "${ciphers_found[i]}"
                    if "$SHOW_EACH_C"; then
                         if "${ciphers_found[i]}"; then
                              pr_cipher_quality "${rfc_ciph[i]}" "available"
                         else
                              pr_deemphasize "not a/v"
                         fi
                    fi
                    outln "${sigalg[i]}"
               fi
          done
          if ! "$WIDE"; then
               if [[ "$COLOR" -le 2 ]]; then
                    out "$(out_row_aligned_max_width "$fs_ciphers" "                              " $TERM_WIDTH)"
               else
                    out_row_aligned_max_width_by_entry "$fs_ciphers" "                              " $TERM_WIDTH pr_cipher_quality
               fi
          fi
          debugme echo $fs_offered
          "$WIDE" || outln
          fileout "${jsonID}_ciphers" "INFO" "$fs_ciphers"
     fi

     # find out what elliptic curves are supported.
     if "$ecdhe_offered"; then
          for curve in "${curves_ossl[@]}"; do
               ossl_supported[nr_curves]=false
               supported_curve[nr_curves]=false
               [[ "$OSSL_SUPPORTED_CURVES" =~ " $curve " ]] && ossl_supported[nr_curves]=true && nr_ossl_curves+=1
               nr_curves+=1
          done

          # OpenSSL limits the number of curves that can be specified in the
          # "-curves" option to 28. So, break the list in two if there are more
          # than 28 curves supported by OpenSSL.
          for j in 1 2; do
               if [[ $j -eq 1 ]]; then
                    if [[ $nr_ossl_curves -le 28 ]]; then
                         low=0; high=$nr_curves
                    else
                         low=0; high=$nr_curves/2
                    fi
               else
                    if [[ $nr_ossl_curves -le 28 ]]; then
                         continue # all curves tested in first round
                    else
                         low=$nr_curves/2; high=$nr_curves
                    fi
               fi
               if "$HAS_TLS13"; then
                    if "$fs_tls13_offered"; then
                         protos_to_try="-no_ssl2 -no_tls1_3"
                    else
                         protos_to_try="-no_tls1_3"
                    fi
               else
                    protos_to_try="-no_ssl2"
               fi

               for proto in $protos_to_try; do
                    while true; do
                         curves_to_test=""
                         for (( i=low; i < high; i++ )); do
                              if ! "$HAS_TLS13" || ! "${curves_deprecated[i]}" || [[ "$proto" == "-no_tls1_3" ]]; then
                                   "${ossl_supported[i]}" && ! "${supported_curve[i]}" && curves_to_test+=":${curves_ossl[i]}"
                              fi
                         done
                         [[ -z "$curves_to_test" ]] && break
                         $OPENSSL s_client $(s_client_options "$proto -cipher "\'${ecdhe_cipher_list:1}\'" -ciphersuites "\'${tls13_cipher_list:1}\'" -curves "${curves_to_test:1}" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") &>$TMPFILE </dev/null
                         sclient_connect_successful $? $TMPFILE || break
                         temp=$(awk -F': ' '/^Server Temp Key/ { print $2 }' "$TMPFILE")
                         curve_found="${temp%%,*}"
                         if [[ "$curve_found" == ECDH ]]; then
                              curve_found="${temp#*, }"
                              curve_found="${curve_found%%,*}"
                         fi
                         for (( i=low; i < high; i++ )); do
                              ! "${supported_curve[i]}" && [[ "${curves_ossl_output[i]}" == "$curve_found" ]] && break
                         done
                         [[ $i -eq $high ]] && break
                         supported_curve[i]=true
                    done
               done
          done
     fi
     if "$ecdhe_offered" && "$using_sockets"; then
          protos_to_try="03"
          "$fs_tls13_offered" && protos_to_try="04 03"
          for proto in $protos_to_try; do
               if [[ "$proto" == 03 ]]; then
                    ecdhe_cipher_list_hex="$(strip_inconsistent_ciphers "03" "$ecdhe_cipher_list_hex")"
                    [[ -z "$ecdhe_cipher_list_hex" ]] && continue
               fi
               while true; do
                    curves_to_test=""
                    for (( i=0; i < nr_curves; i++ )); do
                         if ! "${curves_deprecated[i]}" || [[ "$proto" == 03 ]]; then
                              ! "${supported_curve[i]}" && curves_to_test+=", ${curves_hex[i]}"
                         fi
                    done
                    [[ -z "$curves_to_test" ]] && break
                    len1=$(printf "%02x" "$((2*${#curves_to_test}/7))")
                    len2=$(printf "%02x" "$((2*${#curves_to_test}/7+2))")
                    tls_sockets "$proto" "${ecdhe_cipher_list_hex:2}, 00,ff" "ephemeralkey" "00, 0a, 00, $len2, 00, $len1, ${curves_to_test:2}"
                    sclient_success=$?
                    [[ $sclient_success -ne 0 ]] && [[ $sclient_success -ne 2 ]] && break
                    temp=$(awk -F': ' '/^Server Temp Key/ { print $2 }' "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")
                    curve_found="${temp%%,*}"
                    if [[ "$curve_found" == "ECDH" ]]; then
                         curve_found="${temp#*, }"
                         curve_found="${curve_found%%,*}"
                    fi
                    for (( i=0; i < nr_curves; i++ )); do
                         ! "${supported_curve[i]}" && [[ "${curves_ossl_output[i]}" == "$curve_found" ]] && break
                    done
                    [[ $i -eq $nr_curves ]] && break
                    supported_curve[i]=true
               done
          done
     fi
     if "$ecdhe_offered"; then
          low=1000
          for (( i=0; i < nr_curves; i++ )); do
               if "${supported_curve[i]}"; then
                    curves_offered+="${curves_ossl[i]} "
                    [[ ${curves_bits[i]} -lt $low ]] && low=${curves_bits[i]}
               fi
          done
          if [[ -n "$curves_offered" ]]; then
               "$WIDE" && outln
               pr_bold " Elliptic curves offered:     "
               out_row_aligned_max_width_by_entry "$curves_offered" "                              " $TERM_WIDTH pr_ecdh_curve_quality
               outln
               # severity ratings based on quality specified by
               # pr_ecdh_quality() for shortest curve offered.
               if [[ "$low" -le 163 ]]; then
                    fileout "${jsonID}_ECDHE_curves" "MEDIUM" "$curves_offered"
               elif [[ "$low" -le 193 ]]; then
                    fileout "${jsonID}_ECDHE_curves" "LOW" "$curves_offered"
               elif [[ "$low" -le 224 ]]; then
                    fileout "${jsonID}_ECDHE_curves" "INFO" "$curves_offered"
               else
                    fileout "${jsonID}_ECDHE_curves" "OK" "$curves_offered"
               fi
          fi
     fi
     CURVES_OFFERED="$curves_offered"
     CURVES_OFFERED=$(strip_trailing_space "$CURVES_OFFERED")

     # find out what groups are supported.
     if "$using_sockets" && ( "$fs_tls13_offered" || "$ffdhe_offered" ); then
          nr_curves=0
          for curve in "${ffdhe_groups_output[@]}"; do
               supported_curve[nr_curves]=false
               [[ "$DH_GROUP_OFFERED" =~ $curve ]] && supported_curve[nr_curves]=true
               nr_curves+=1
          done
          protos_to_try=""
          "$fs_tls13_offered" && protos_to_try="04"
          if "$ffdhe_offered"; then
               if "$fs_tls13_offered"; then
                    protos_to_try="04 03"
               else
                    protos_to_try="03"
               fi
          fi
          curve_found=""
          for proto in $protos_to_try; do
               while true; do
                    curves_to_test=""
                    for (( i=0; i < nr_curves; i++ )); do
                         ! "${supported_curve[i]}" && curves_to_test+=", ${ffdhe_groups_hex[i]}"
                    done
                    [[ -z "$curves_to_test" ]] && break
                    len1=$(printf "%02x" "$((2*${#curves_to_test}/7))")
                    len2=$(printf "%02x" "$((2*${#curves_to_test}/7+2))")
                    tls_sockets "$proto" "${ffdhe_cipher_list_hex:2}, 00,ff" "ephemeralkey" "00, 0a, 00, $len2, 00, $len1, ${curves_to_test:2}"
                    sclient_success=$?
                    [[ $sclient_success -ne 0 ]] && [[ $sclient_success -ne 2 ]] && break
                    temp=$(awk -F': ' '/^Server Temp Key/ { print $2 }' "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")
                    curve_found="${temp#*, }"
                    curve_found="${curve_found%%,*}"
                    if [[ "$proto" == "03" ]] && [[ -z "$DH_GROUP_OFFERED" ]] && [[ "$curve_found" =~ ffdhe ]]; then
                         DH_GROUP_OFFERED="RFC7919/$curve_found"
                         DH_GROUP_LEN_P="${curve_found#ffdhe}"
                    fi
                    [[ ! "$curve_found" =~ ffdhe ]] && break
                    for (( i=0; i < nr_curves; i++ )); do
                         ! "${supported_curve[i]}" && [[ "${ffdhe_groups_output[i]}" == "$curve_found" ]] && break
                    done
                    [[ $i -eq $nr_curves ]] && break
                    supported_curve[i]=true
               done
          done
          curves_offered=""
          for (( i=0; i < nr_curves; i++ )); do
               "${supported_curve[i]}" && curves_offered+="${ffdhe_groups_output[i]} "
          done
          curves_offered="$(strip_trailing_space "$curves_offered")"
          if "$ffdhe_offered" && [[ -z "$curves_offered" ]] && [[ -z "$curve_found" ]]; then
               # Some servers will fail if the supported_groups extension is present.
               tls_sockets "03" "${ffdhe_cipher_list_hex:2}, 00,ff" "ephemeralkey"
               sclient_success=$?
               if [[ $sclient_success -eq 0 ]] || [[ $sclient_success -eq 2 ]]; then
                    temp=$(awk -F': ' '/^Server Temp Key/ { print $2 }' "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")
                    curve_found="${temp#*, }"
                    curve_found="${curve_found%%,*}"
               fi
          fi
          if [[ -z "$curves_offered" ]] && [[ -n "$curve_found" ]]; then
               # The server is not using one of the groups from RFC 7919.
               if [[ -z "$DH_GROUP_OFFERED" ]]; then
                    # this global will get the name of the group either here or in run_logjam()
                    key_bitstring="$(awk '/-----BEGIN PUBLIC KEY/,/-----END PUBLIC KEY/ { print $0 }' $TEMPDIR/$NODEIP.parse_tls_serverhello.txt)"
                    get_common_prime "$jsonID" "$key_bitstring" ""
                    case $? in
                         0) curves_offered="$DH_GROUP_OFFERED"
                              len_dh_p=$DH_GROUP_LEN_P ;;
                         2) pr_bold " DH or FF group offered :     "
                              prln_local_problem "Your $OPENSSL does not support the pkey utility."
                              fileout "$jsonID" "WARN" "$OPENSSL does not support the pkey utility."
                         esac
               else
                    curves_offered="$DH_GROUP_OFFERED"
                    len_dh_p=$DH_GROUP_LEN_P
               fi
          fi
          if [[ -n "$curves_offered" ]]; then
               if [[ ! "$curves_offered" =~ ffdhe ]] || [[ ! "$curves_offered" =~ \  ]]; then
                    pr_bold " DH group offered:            "
               else
                    pr_bold " Finite field group:          "
               fi
               if [[ "$curves_offered" =~ ffdhe ]]; then
                    # ok not to display them in italics:
                    pr_svrty_good "$curves_offered"
                    quality=6
               else
                    pr_dh "$curves_offered" "$len_dh_p"
                    quality=$?
               fi
               case "$quality" in
                    1) quality_str="CRITICAL" ;;
                    2) quality_str="HIGH" ;;
                    3) quality_str="MEDIUM" ;;
                    4) quality_str="LOW" ;;
                    5) quality_str="INFO" ;;
                    6|7) quality_str="OK" ;;
               esac
               if [[ "$curves_offered" =~ Unknown ]]; then
                    fileout "DH_groups" "$quality_str" "$curves_offered ($len_dh_p bits)"
               else
                    fileout "DH_groups" "$quality_str" "$curves_offered"
               fi
          fi
     fi
     outln

     tmpfile_handle ${FUNCNAME[0]}.txt
     "$using_sockets" && HAS_DH_BITS="$has_dh_bits"
     return 0
}


# good source for configuration and bugs: https://wiki.mozilla.org/Security/Server_Side_TLS
# good start to read: https://en.wikipedia.org/wiki/Transport_Layer_Security#Attacks_against_TLS.2FSSL


npn_pre(){
     if [[ -n "$PROXY" ]]; then
          pr_warning "not tested as proxies do not support proxying it"
          fileout "NPN" "WARN" "not tested as proxies do not support proxying it"
          return 1
     fi
     if ! "$HAS_NPN"; then
          pr_local_problem "$OPENSSL doesn't support NPN/SPDY";
          fileout "NPN" "WARN" "not tested $OPENSSL doesn't support NPN/SPDY"
          return 7
     fi
     return 0
}

alpn_pre(){
     if [[ -n "$PROXY" ]]; then
          pr_warning "not tested as proxies do not support proxying it"
          fileout "ALPN" "WARN" "not tested as proxies do not support proxying it"
          return 1
     fi
     if ! "$HAS_ALPN" && "$SSL_NATIVE"; then
          prln_local_problem "$OPENSSL doesn't support ALPN/HTTP2";
          fileout "ALPN" "WARN" "not tested as $OPENSSL does not support it"
          return 7
     fi
     return 0
}

# modern browsers do not support it anymore but we should still test it at least for fingerprinting the server side
# Thus we don't label any support for NPN as good.
# FAST mode skips this test
run_npn() {
     local tmpstr
     local -i ret=0
     local jsonID="NPN"

     [[ -n "$STARTTLS" ]] && return 0
     "$FAST" && return 0
     pr_bold " NPN/SPDY   "
     if ! npn_pre; then
          outln
          return 0
     fi
     $OPENSSL s_client $(s_client_options "-connect $NODEIP:$PORT $BUGS $SNI -nextprotoneg "$NPN_PROTOs"") </dev/null 2>$ERRFILE >$TMPFILE
     [[ $? -ne 0 ]] && ret=1
     tmpstr="$(grep -a '^Protocols' $TMPFILE | sed 's/Protocols.*: //')"
     if [[ -z "$tmpstr" ]] || [[ "$tmpstr" == " " ]]; then
          outln "not offered"
          fileout "$jsonID" "INFO" "not offered"
     else
          # now comes a strange thing: "Protocols advertised by server:" is empty but connection succeeded
          if [[ "$tmpstr" =~ [h2|spdy|http] ]]; then
               out "$tmpstr"
               outln " (advertised)"
               fileout "$jsonID" "INFO" "offered with $tmpstr (advertised)"
          else
               prln_cyan "please check manually, server response was ambiguous ..."
               fileout "$jsonID" "INFO" "please check manually, server response was ambiguous ..."
               ((ret++))
          fi
     fi
     # btw: nmap can do that too https://nmap.org/nsedoc/scripts/tls-nextprotoneg.html
     # nmap --script=tls-nextprotoneg #NODE -p $PORT is your friend if your openssl doesn't want to test this
     tmpfile_handle ${FUNCNAME[0]}.txt
     return $ret
}


run_alpn() {
     local tmpstr alpn_extn len
     local -i ret=0
     local has_alpn_proto=false
     local alpn_finding=""
     local jsonID="ALPN"

     [[ -n "$STARTTLS" ]] && return 0
     pr_bold " ALPN/HTTP2 "
     if ! alpn_pre; then
          outln
          return 0
     fi
     for proto in $ALPN_PROTOs; do
          # for some reason OpenSSL doesn't list the advertised protocols, so instead try common protocols
          if "$HAS_ALPN"; then
               $OPENSSL s_client $(s_client_options "-connect $NODEIP:$PORT $BUGS $SNI -alpn $proto") </dev/null 2>$ERRFILE >$TMPFILE
          else
               alpn_extn="$(printf "%02x" ${#proto}),$(string_to_asciihex "$proto")"
               len="$(printf "%04x" $((${#proto}+1)))"
               alpn_extn="${len:0:2},${len:2:2},$alpn_extn"
               len="$(printf "%04x" $((${#proto}+3)))"
               alpn_extn="00,10,${len:0:2},${len:2:2},$alpn_extn"
               tls_sockets "03" "$TLS12_CIPHER" "all+" "$alpn_extn"
               if [[ -r "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" ]]; then
                    cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" $TMPFILE
               else
                    echo "" > $TMPFILE
               fi
          fi
          tmpstr="$(awk -F':' '/^ALPN protocol*:/ { print $2 }' $TMPFILE)"
          if [[ "$tmpstr" == *"$proto" ]]; then
               if ! $has_alpn_proto; then
                    has_alpn_proto=true
               else
                    out ", "
               fi
               # only h2 is what browser need to use HTTP/2.0 and brings a security,privacy and performance benefit
               if [[ "$proto" == "h2" ]]; then
                    pr_svrty_good "$proto"
                    fileout "${jsonID}_HTTP2" "OK" "$proto"
               else
                    out "$proto"
                    alpn_finding+="$proto"
               fi
          fi
     done
     if $has_alpn_proto; then
          outln " (offered)"
          # if h2 is not the only protocol:
          [[ -n "$alpn_finding" ]] && fileout "$jsonID" "INFO" "$alpn_finding"
     else
          outln "not offered"
          fileout "$jsonID" "INFO" "not offered"
     fi
     tmpfile_handle ${FUNCNAME[0]}.txt
     return $ret
}

# arg1: send string
# arg2: success string: an egrep pattern
# arg3: number of loops we should read from the buffer (optional, otherwise STARTTLS_SLEEP)
starttls_io() {
     local nr_waits=$STARTTLS_SLEEP
     local buffer=""
     local -i i

     [[ -n "$3" ]] && waitsleep=$3
     [[ -z "$2" ]] && echo "FIXME $((LINENO))"

     # If there's a sending part it's IO. Postgres sends via socket and replies via
     # strings "S". So there's no I part of IO ;-)
     if [[ -n "$1" ]]; then
          debugme echo -en "C: $1"
          echo -en "$1" >&5
     fi
     if [[ "$2" == JUSTSEND ]]; then
          debugme echo -e "\n  (only sent)\n"
          dd of=/dev/null bs=512 count=1 <&5 2>/dev/null &
          return 0
     fi

     # This seems a bit dangerous but works. No blockings yet. "if=nonblock" doesn't work on BSDs
     buffer="$(dd bs=512 count=1 <&5 2>/dev/null)"

     for ((i=1; i < $nr_waits; i++ )); do
          [[ "$DEBUG" -ge 2 ]] && echo -en "\nS: " && echo $buffer
          if [[ "$buffer" =~ $2 ]]; then
               debugme echo "     ---> reply matched \"$2\""
               # the fd sometimes still seem to contain chars which confuses the following TLS handshake, trying to empty:
               # dd of=/dev/null bs=512 count=1 <&5 2>/dev/null
               return 0
          else
               # no match yet, more reading from fd helps.
               buffer+=$(dd bs=512 count=1 <&5 2>/dev/null)
          fi
     done
     return 1
}


# Line-based send with newline characters appended (arg2 empty)
# Stream-based send: arg2: <any>
starttls_just_send(){
     if [[ -z "$2" ]] ; then
          debugme echo -e "C: $1 plus lf"
          echo -ne "$1\r\n" >&5
     else
          debugme echo -e "C: $1"
          echo -ne "$1" >&5
     fi
     return $?
}

# arg1: (optional): wait time
starttls_just_read(){
     local waitsleep=$STARTTLS_SLEEP
     [[ -n "$1" ]] && waitsleep=$1
     if [[ "$DEBUG" -ge 2 ]]; then
          echo "=== just read banner ==="
          cat <&5 &
     else
          dd of=/dev/null count=8 <&5 2>/dev/null &
     fi
     wait_kill $! $waitsleep
     return 0
}

starttls_full_read(){
     local starttls_read_data=()
     local one_line=""
     local ret=0
     local cont_pattern="$1"
     local end_pattern="$2"
     local ret_found=0

     debugme echo "=== reading banner ... ==="
     if [[ $# -ge 3 ]]; then
          debugme echo "=== we'll have to search for \"$3\" pattern ==="
          ret_found=3
     fi

     local oldIFS="$IFS"
     IFS=''
     while read -r -t $STARTTLS_SLEEP one_line; ret=$?; (exit $ret); do
          debugme echo "S: ${one_line}"
          if [[ $# -ge 3 ]]; then
               if [[ ${one_line} =~ $3 ]]; then
                    ret_found=0
                    debugme echo "^^^^^^^ that's what we were looking for ==="
               fi
          fi
          starttls_read_data+=("${one_line}")
          if [[ $DEBUG -ge 4 ]]; then
               echo "one_line: ${one_line}"
               echo "end_pattern: ${end_pattern}"
               echo "cont_pattern: ${cont_pattern}"
          fi
          if [[ ${one_line} =~ ${end_pattern} ]]; then
               debugme echo "=== full read finished ==="
               IFS="${oldIFS}"
               return ${ret_found}
          fi
          if [[ ! ${one_line} =~ ${cont_pattern} ]]; then
               debugme echo "=== full read syntax error, expected regex pattern ${cont_pattern} (cont) or ${end_pattern} (end) ==="
               IFS="${oldIFS}"
               return 2
          fi
     done <&5
     if [[ $DEBUG -ge 2 ]]; then
          if [[ $ret -ge 128 ]]; then
               echo "=== timeout reading ==="
          else
               echo "=== full read error (no timeout) ==="
          fi
     fi
     IFS="${oldIFS}"
     return $ret
}

starttls_ftp_dialog() {
     debugme echo "=== starting ftp STARTTLS dialog ==="
     local reAUTHTLS='^ AUTH TLS'
     starttls_full_read '^220-' '^220 '                    && debugme echo "received server greeting" &&
     starttls_just_send 'FEAT'                             && debugme echo "sent FEAT" &&
     starttls_full_read '^(211-| )' '^211 ' "${reAUTHTLS}" && debugme echo "received server features and checked STARTTLS availability" &&
     starttls_just_send 'AUTH TLS'                         && debugme echo "initiated STARTTLS" &&
     starttls_full_read '^234-' '^234 '                    && debugme echo "received ack for STARTTLS"
     local ret=$?
     debugme echo "=== finished ftp STARTTLS dialog with ${ret} ==="
     return $ret
}

# argv1: empty: SMTP, "lmtp" : LMTP
#
starttls_smtp_dialog() {
     local greet_str="EHLO"
     local proto="smtp"

     if [[ "$1" == lmtp ]]; then
          proto="lmtp"
          greet_str="LHLO"
     fi
     debugme echo "=== starting $proto STARTTLS dialog ==="

     local re250STARTTLS='^250[ -]STARTTLS'
     starttls_full_read '^220-' '^220 '                    && debugme echo "received server greeting" &&
     starttls_just_send "$greet_str testssl.sh"            && debugme echo "sent $greet_str" &&
     starttls_full_read '^250-' '^250 ' "${re250STARTTLS}" && debugme echo "received server capabilities and checked STARTTLS availability" &&
     starttls_just_send 'STARTTLS'                         && debugme echo "initiated STARTTLS" &&
     starttls_full_read '^220-' '^220 '                    && debugme echo "received ack for STARTTLS"
     local ret=$?
     debugme echo "=== finished $proto STARTTLS dialog with ${ret} ==="
     return $ret
}

starttls_pop3_dialog() {
     debugme echo "=== starting pop3 STARTTLS dialog ==="
     starttls_full_read '^\+OK' '^\+OK'                    && debugme echo "received server greeting" &&
     starttls_just_send 'STLS'                             && debugme echo "initiated STARTTLS" &&
     starttls_full_read '^\+OK' '^\+OK'                    && debugme echo "received ack for STARTTLS"
     local ret=$?
     debugme echo "=== finished pop3 STARTTLS dialog with ${ret} ==="
     return $ret
}

starttls_imap_dialog() {
     debugme echo "=== starting imap STARTTLS dialog ==="
     local reSTARTTLS='^\* CAPABILITY(( .*)? IMAP4rev1( .*)? STARTTLS(.*)?|( .*)? STARTTLS( .*)? IMAP4rev1(.*)?)$'
     starttls_full_read '^\* ' '^\* OK '                   && debugme echo "received server greeting" &&
     starttls_just_send 'a001 CAPABILITY'                  && debugme echo "sent CAPABILITY" &&
     starttls_full_read '^\* ' '^a001 OK ' "${reSTARTTLS}" && debugme echo "received server capabilities and checked STARTTLS availability" &&
     starttls_just_send 'a002 STARTTLS'                    && debugme echo "initiated STARTTLS" &&
     starttls_full_read '^\* ' '^a002 OK '                 && debugme echo "received ack for STARTTLS"
     local ret=$?
     debugme echo "=== finished imap STARTTLS dialog with ${ret} ==="
     return $ret
}

starttls_xmpp_dialog() {
     debugme echo "=== starting xmpp STARTTLS dialog ==="
     [[ -z $XMPP_HOST ]] && XMPP_HOST="$NODE"

     namespace="jabber:client"
     [[ "$STARTTLS_PROTOCOL" == xmpp-server ]] && namespace="jabber:server"

     starttls_io "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='"$namespace"' to='"$XMPP_HOST"' version='1.0'>"  'starttls(.*)features' 1 &&
     starttls_io "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"  '<proceed'  1
     # starttls_io "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='"$namespace"' to='"$XMPP_HOST"' version='1.0'>"  'JUSTSEND' 2
     local ret=$?
     debugme echo "=== finished xmpp STARTTLS dialog with ${ret} ==="
     return $ret
}

starttls_nntp_dialog() {
     debugme echo "=== starting nntp STARTTLS dialog ==="
     starttls_full_read '$^' '^20[01] '                    && debugme echo "received server greeting" &&
     starttls_just_send 'STARTTLS'                         && debugme echo "initiated STARTTLS" &&
     starttls_full_read '$^' '^382 '                       && debugme echo "received ack for STARTTLS"
     local ret=$?
     debugme echo "=== finished nntp STARTTLS dialog with ${ret} ==="
     return $ret
}

starttls_postgres_dialog() {
     debugme echo "=== starting postgres STARTTLS dialog ==="
     local init_tls=", x00, x00 ,x00 ,x08 ,x04 ,xD2 ,x16 ,x2F"
     socksend "${init_tls}" 0                               && debugme echo "initiated STARTTLS" &&
     starttls_io "" S 1                                     && debugme echo "received ack (="S") for STARTTLS"
     local ret=$?
     debugme echo "=== finished postgres STARTTLS dialog with ${ret} ==="
     return $ret
}

starttls_mysql_dialog() {
     debugme echo "=== starting mysql STARTTLS dialog ==="
     local login_request="
     , x20, x00, x00, x01,                   # payload_length, sequence_id
     x85, xae, xff, x00,                     # capability flags, CLIENT_SSL always set
     x00, x00, x00, x01,                     # max-packet size
     x21,                                    # character set
     x00, x00, x00, x00, x00, x00, x00, x00, # string[23] reserved (all [0])
     x00, x00, x00, x00, x00, x00, x00, x00,
     x00, x00, x00, x00, x00, x00, x00"
     socksend "${login_request}" 0
     starttls_just_read 1                    && debugme echo "read succeeded"
     # 1 is the timeout value which only MySQL needs. Note, there seems no response whether STARTTLS
     # succeeded. We could try harder, see https://github.com/openssl/openssl/blob/master/apps/s_client.c
     # but atm this seems sufficient as later we will fail if there's no STARTTLS.
     # BUT: there seeem to be cases when the handshake fails (8S01Bad handshake --> 30 38 53 30 31 42 61 64 20 68 61 6e 64 73 68 61 6b 65).
     #      also there's a banner in the reply "<version><somebytes>mysql_native_password"
     # TODO: We could detect if the server supports STARTTLS via the "Server Capabilities"
     # bit field, but we'd need to parse the binary stream, with greater precision than regex.
     local ret=$?
     debugme echo "=== finished mysql STARTTLS dialog with ${ret} ==="
     return $ret
}

# arg1: fd for socket -- which we don't use as it is a hassle and it is not clear whether it works under every bash version
# returns 6 if opening the socket caused a problem, 1 if STARTTLS handshake failed, 0: all ok
#
fd_socket() {
     local jabber=""
     local proyxline=""
     local nodeip="$(tr -d '[]' <<< $NODEIP)"          # sockets do not need the square brackets we have of IPv6 addresses
                                                       # we just need do it here, that's all!
     if [[ -t 5 ]]; then
          pr_warning "$PROG_NAME: unable to open a socket because of a tty conflict"
          return 6
     fi
     if [[ -n "$PROXY" ]]; then
          # PROXYNODE works better than PROXYIP on modern versions of squid
          if ! exec 5<> /dev/tcp/${PROXYNODE}/${PROXYPORT}; then
               outln
               pr_warning "$PROG_NAME: unable to open a socket to proxy $PROXYNODE:$PROXYPORT"
               return 6
          fi
          if "$DNS_VIA_PROXY"; then
               printf -- "%b" "CONNECT $NODE:$PORT HTTP/1.0\n\n" >&5
          else
               printf -- "%b" "CONNECT $nodeip:$PORT HTTP/1.0\n\n" >&5
          fi
          while true; do
               read -t $PROXY_WAIT -r proyxline <&5
               if [[ $? -ge 128 ]]; then
                    pr_warning "Proxy timed out. Unable to CONNECT via proxy. "
                    close_socket
                    return 6
               elif [[ "${proyxline%/*}" == HTTP ]]; then
                    proyxline=${proyxline#* }
                    if [[ "${proyxline%% *}" != 200 ]]; then
                         pr_warning "Unable to CONNECT via proxy. "
                         [[ "$PORT" != 443 ]] && prln_warning "Check whether your proxy supports port $PORT and the underlying protocol."
                         close_socket
                         return 6
                    fi
               fi
               if [[ "$proyxline" == $'\r' ]] || [[ -z "$proyxline" ]] ; then
                    break
               fi
          done
     # For the following execs: 2>/dev/null would remove a potential error message, but disables debugging.
     # First we check whether a socket connect timeout was specified
     elif [[ -n "$CONNECT_TIMEOUT" ]]; then
          if ! $TIMEOUT_CMD $CONNECT_TIMEOUT bash -c "exec 5<>/dev/tcp/$nodeip/$PORT"; then
               ((NR_SOCKET_FAIL++))
               connectivity_problem $NR_SOCKET_FAIL $MAX_SOCKET_FAIL "TCP connect problem" "repeated TCP connect problems (connect timeout), giving up"
               outln
               pr_warning "Unable to open a socket to $NODEIP:$PORT. "
               return 6
          fi
     # Now comes the the usual case
     elif ! exec 5<>/dev/tcp/$nodeip/$PORT; then
          ((NR_SOCKET_FAIL++))
          connectivity_problem $NR_SOCKET_FAIL $MAX_SOCKET_FAIL "TCP connect problem" "repeated TCP connect problems, giving up"
          outln
          pr_warning "Unable to open a socket to $NODEIP:$PORT. "
          return 6
     fi

     if [[ -n "$STARTTLS" ]]; then
          case "$STARTTLS_PROTOCOL" in # port
               ftp|ftps)   # https://tools.ietf.org/html/rfc4217, https://tools.ietf.org/html/rfc959
                    starttls_ftp_dialog
                    ;;
               smtp|smtps) # SMTP, see https://tools.ietf.org/html/rfc{2033,3207,5321}
                    starttls_smtp_dialog
                    ;;
               lmtp|lmtps) # LMTP, see https://tools.ietf.org/html/rfc{2033,3207,5321}
                    starttls_smtp_dialog lmtp
                    ;;
               pop3|pop3s) # POP, see https://tools.ietf.org/html/rfc2595
                    starttls_pop3_dialog
                    ;;
               nntp|nntps) # NNTP, see https://tools.ietf.org/html/rfc4642
                    starttls_nntp_dialog
                    ;;
               imap|imaps) # IMAP, https://tools.ietf.org/html/rfc2595, https://tools.ietf.org/html/rfc3501
                    starttls_imap_dialog
                    ;;
               irc|ircs) # IRC, https://ircv3.net/specs/extensions/tls-3.1.html, https://ircv3.net/specs/core/capability-negotiation.html
                    fatal "FIXME: IRC+STARTTLS not yet supported" $ERR_NOSUPPORT
                    ;;
               ldap|ldaps) # LDAP, https://tools.ietf.org/html/rfc2830, https://tools.ietf.org/html/rfc4511
                    fatal "FIXME: LDAP+STARTTLS over sockets not supported yet (try \"--ssl-native\")" $ERR_NOSUPPORT
                    ;;
               acap|acaps) # ACAP = Application Configuration Access Protocol, see https://tools.ietf.org/html/rfc2595
                    fatal "ACAP Easteregg: not implemented -- probably never will" $ERR_NOSUPPORT
                    ;;
               xmpp|xmpps|xmpp-server) # XMPP, see https://tools.ietf.org/html/rfc6120
                    starttls_xmpp_dialog
                    # IM observatory: https://xmpp.net , XMPP server directory: https://xmpp.net/directory.php
                    ;;
               postgres) # Postgres SQL, see https://www.postgresql.org/docs/devel/protocol-message-formats.html
                    starttls_postgres_dialog
                    ;;
               mysql) # MySQL, see https://dev.mysql.com/doc/internals/en/x-protocol-lifecycle-lifecycle.html#x-protocol-lifecycle-tls-extension
                    starttls_mysql_dialog
                    ;;
               *) # we need to throw an error here -- otherwise testssl.sh treats the STARTTLS protocol as plain SSL/TLS which leads to FP
                    fatal "FIXME: STARTTLS protocol $STARTTLS_PROTOCOL is not yet supported" $ERR_NOSUPPORT
          esac
     fi
     [[ $? -eq 0 ]] && return 0
     prln_warning " STARTTLS handshake failed"
     return 1
}

close_socket(){
     exec 5<&-
     exec 5>&-
     return 0
}

send_close_notify() {
     local detected_tlsversion="$1"

     debugme echo "sending close_notify..."
     if [[ $detected_tlsversion == 0300 ]]; then
          socksend ",x15, x03, x00, x00, x02, x02, x00" 0
     else
          socksend ",x15, x03, x01, x00, x02, x02, x00" 0
     fi
}

# Format string properly for socket
# ARG1: any commented sequence of two bytes hex, separated by commas. It can contain comments, new lines, tabs and white spaces
# NW_STR holds the global with the string prepared for printf, like '\x16\x03\x03\'
code2network() {
     NW_STR=$(sed -e 's/,/\\\x/g' <<< "$1" | sed -e 's/# .*$//g' -e 's/ //g' -e '/^$/d' | tr -d '\n' | tr -d '\t')
}

# sockets inspired by https://blog.chris007.de/using-bash-for-network-socket-operation/
# ARG1: hexbytes separated by commas, with a leading comma
# ARG2: seconds to sleep
socksend_clienthello() {
     local data=""

     code2network "$1"
     data="$NW_STR"
     [[ "$DEBUG" -ge 4 ]] && echo && echo "\"$data\""
     if [[ -z "$PRINTF" ]] ;then
          # We could also use "dd ibs=1M obs=1M" here but is seems to be at max 3% slower
          printf -- "$data" | cat >&5 2>/dev/null &
     else
          $PRINTF -- "$data" 2>/dev/null >&5 2>/dev/null &
     fi
     sleep $USLEEP_SND
}


# ARG1: hexbytes -- preceeded by x -- separated by commas, with a leading comma
# ARG2: seconds to sleep
socksend() {
     local data line

     # read line per line and strip comments (bash internal func can't handle multiline statements
     data="$(while read line; do
          printf "${line%%\#*}"
     done <<< "$1" )"
     data="${data// /}"       # strip ' '
     data="${data//,/\\}"     # s&r , by \
     [[ $DEBUG -ge 4 ]] && echo && echo "\"$data\""
     if [[ -z "$PRINTF" ]] ;then
          printf -- "$data" | cat >&5 2>/dev/null &
     else
          $PRINTF -- "$data" 2>/dev/null >&5 2>/dev/null &
     fi
     sleep $2
}


# for SSLv2 to TLS 1.2:
# ARG1: blocksize for reading
sockread_serverhello() {
     [[ -z "$2" ]] && maxsleep=$MAX_WAITSOCK || maxsleep=$2
     SOCK_REPLY_FILE=$(mktemp $TEMPDIR/ddreply.XXXXXX) || return 7
     dd bs=$1 of=$SOCK_REPLY_FILE count=1 <&5 2>/dev/null &
     wait_kill $! $maxsleep
     return $?
}

#trying a faster version
# ARG1: blocksize for reading
sockread_fast() {
     dd bs=$1 count=1 <&5 2>/dev/null | hexdump -v -e '16/1 "%02X"'
}

len2twobytes() {
     local len_arg1=${#1}
     [[ $len_arg1 -le 2 ]] && LEN_STR=$(printf "00, %02s \n" "$1")
     [[ $len_arg1 -eq 3 ]] && LEN_STR=$(printf "0%s, %02s \n" "${1:0:1}" "${1:1:2}")
     [[ $len_arg1 -eq 4 ]] && LEN_STR=$(printf "%02s, %02s \n" "${1:0:2}" "${1:2:2}")
}


get_pub_key_size() {
     local pubkey pubkeybits
     local -i i len1 len

     "$HAS_PKEY" || return 1

     # OpenSSL displays the number of bits for RSA and ECC
     pubkeybits=$($OPENSSL x509 -noout -pubkey -in $HOSTCERT 2>>$ERRFILE | $OPENSSL pkey -pubin -text 2>>$ERRFILE | awk -F'(' '/Public-Key/ { print $2 }')
     if [[ -n $pubkeybits ]]; then
          # remainder e.g. "256 bit)"
          pubkeybits="${pubkeybits//\)/}"
          echo "Server public key is $pubkeybits" >> $TMPFILE
     else
          # This extracts the public key for DSA, DH, and GOST
          pubkey=$($OPENSSL x509 -noout -pubkey -in $HOSTCERT 2>>$ERRFILE | $OPENSSL pkey -pubin -outform DER 2>>$ERRFILE | hexdump -v -e '16/1 "%02X"')
          [[ -z "$pubkey" ]] && return 1
          # Skip over tag and length of subjectPublicKeyInfo
          i=2
          len1="0x${pubkey:i:2}"
          if [[ $len1 -lt 0x80 ]]; then
               i+=2
          else
               len1=$((len1-0x80))
               i+=$((2*len1+2))
          fi

          # Skip over algorithm field
          i+=2
          len1="0x${pubkey:i:2}"
          i+=2
          if [[ $len1 -lt 0x80 ]]; then
               i+=$((2*len1))
          else
               case $len1 in
                    129) len="0x${pubkey:i:2}" ;;
                    130) len="0x${pubkey:i:2}"
                         i+=2
                         len=256*$len+"0x${pubkey:i:2}"
                         ;;
                    131) len="0x${pubkey:i:2}"
                         i+=2
                         len=256*$len+"0x${pubkey:i:2}"
                         i+=2
                         len=256*$len+"0x${pubkey:i:2}"
                         ;;
                    132) len="0x${pubkey:i:2}"
                         i+=2
                         len=256*$len+"0x${pubkey:i:2}"
                         i+=2
                         len=256*$len+"0x${pubkey:i:2}"
                         i+=2
                         len=256*$len+"0x${pubkey:i:2}"
                         ;;
               esac
               i+=$((2+2*len))
          fi

          # Next is the public key BIT STRING. Skip over tag, length, and number of unused bits.
          i+=2
          len1="0x${pubkey:i:2}"
          if [[ $len1 -lt 0x80 ]]; then
               i+=4
          else
               len1=$((len1-0x80))
               i+=$((2*len1+4))
          fi

          # Now get the length of the public key
          i+=2
          len1="0x${pubkey:i:2}"
          i+=2
          if [[ $len1 -lt 0x80 ]]; then
               len=$len1
          else
               case $len1 in
                    129) len="0x${pubkey:i:2}" ;;
                    130) len="0x${pubkey:i:2}"
                         i+=2
                         len=256*$len+"0x${pubkey:i:2}"
                         ;;
                    131) len="0x${pubkey:i:2}"
                         i+=2
                         len=256*$len+"0x${pubkey:i:2}"
                         i+=2
                         len=256*$len+"0x${pubkey:i:2}"
                         ;;
                    132) len="0x${pubkey:i:2}"
                         i+=2
                         len=256*$len+"0x${pubkey:i:2}"
                         i+=2
                         len=256*$len+"0x${pubkey:i:2}"
                         i+=2
                         len=256*$len+"0x${pubkey:i:2}"
                         ;;
               esac
          fi
          len=$((8*len)) # convert from bytes to bits
          pubkeybits="$(printf "%d" $len)"
          echo "Server public key is $pubkeybits bit" >> $TMPFILE
     fi
     return 0
}

# Extract the DH ephemeral key from the ServerKeyExchange message
get_dh_ephemeralkey() {
     local tls_serverkeyexchange_ascii="$1"
     local -i tls_serverkeyexchange_ascii_len offset
     local dh_p dh_g dh_y dh_param len1 key_bitstring
     local -i i dh_p_len dh_g_len dh_y_len dh_param_len

     "$HAS_PKEY" || return 1

     tls_serverkeyexchange_ascii_len=${#tls_serverkeyexchange_ascii}
     dh_p_len=2*$(hex2dec "${tls_serverkeyexchange_ascii:0:4}")
     offset=4+$dh_p_len
     if [[ $tls_serverkeyexchange_ascii_len -lt $offset ]]; then
          debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello."
          return 1
     fi

     # Subtract any leading 0 bytes
     for (( i=4; i < offset; i+=2 )); do
          [[ "${tls_serverkeyexchange_ascii:i:2}" != "00" ]] && break
          dh_p_len=$dh_p_len-2
     done
     if [[ $i -ge $offset ]]; then
          debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello."
          return 1
     fi
     dh_p="${tls_serverkeyexchange_ascii:i:dh_p_len}"

     dh_g_len=2*$(hex2dec "${tls_serverkeyexchange_ascii:offset:4}")
     i=4+$offset
     offset+=4+$dh_g_len
     if [[ $tls_serverkeyexchange_ascii_len -lt $offset ]]; then
          debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello."
          return 1
     fi
     # Subtract any leading 0 bytes
     for (( 1; i < offset; i+=2 )); do
          [[ "${tls_serverkeyexchange_ascii:i:2}" != "00" ]] && break
          dh_g_len=$dh_g_len-2
     done
     if [[ $i -ge $offset ]]; then
          debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello."
          return 1
     fi
     dh_g="${tls_serverkeyexchange_ascii:i:dh_g_len}"

     dh_y_len=2*$(hex2dec "${tls_serverkeyexchange_ascii:offset:4}")
     i=4+$offset
     offset+=4+$dh_y_len
     if [[ $tls_serverkeyexchange_ascii_len -lt $offset ]]; then
          debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello."
          return 1
     fi
     # Subtract any leading 0 bytes
     for (( 1; i < offset; i+=2 )); do
          [[ "${tls_serverkeyexchange_ascii:i:2}" != "00" ]] && break
          dh_y_len=$dh_y_len-2
     done
     if [[ $i -ge $offset ]]; then
          debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello."
          return 1
     fi
     dh_y="${tls_serverkeyexchange_ascii:i:dh_y_len}"

     # The following code assumes that all lengths can be encoded using at most 2 bytes,
     # which just means that the encoded length of the public key must be less than
     # 65,536 bytes. If the length is anywhere close to that, it is almost certainly an
     # encoding error.
     if [[ $dh_p_len+$dh_g_len+$dh_y_len -ge 131000 ]]; then
          debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello."
          return 1
     fi
     # make ASN.1 INTEGER of p, g, and Y
     [[ "0x${dh_p:0:1}" -ge 8 ]] && dh_p_len+=2 && dh_p="00$dh_p"
     if [[ $dh_p_len -lt 256 ]]; then
          len1="$(printf "%02x" $((dh_p_len/2)))"
     elif [[ $dh_p_len -lt 512 ]]; then
          len1="81$(printf "%02x" $((dh_p_len/2)))"
     else
          len1="82$(printf "%04x" $((dh_p_len/2)))"
     fi
     dh_p="02${len1}$dh_p"

     [[ "0x${dh_g:0:1}" -ge 8 ]] && dh_g_len+=2 && dh_g="00$dh_g"
     if [[ $dh_g_len -lt 256 ]]; then
          len1="$(printf "%02x" $((dh_g_len/2)))"
     elif [[ $dh_g_len -lt 512 ]]; then
          len1="81$(printf "%02x" $((dh_g_len/2)))"
     else
          len1="82$(printf "%04x" $((dh_g_len/2)))"
     fi
     dh_g="02${len1}$dh_g"

     [[ "0x${dh_y:0:1}" -ge 8 ]] && dh_y_len+=2 && dh_y="00$dh_y"
     if [[ $dh_y_len -lt 256 ]]; then
          len1="$(printf "%02x" $((dh_y_len/2)))"
     elif [[ $dh_y_len -lt 512 ]]; then
          len1="81$(printf "%02x" $((dh_y_len/2)))"
     else
          len1="82$(printf "%04x" $((dh_y_len/2)))"
     fi
     dh_y="02${len1}$dh_y"

     # Make a SEQUENCE of p and g
     dh_param_len=${#dh_p}+${#dh_g}
     if [[ $dh_param_len -lt 256 ]]; then
          len1="$(printf "%02x" $((dh_param_len/2)))"
     elif [[ $dh_param_len -lt 512 ]]; then
          len1="81$(printf "%02x" $((dh_param_len/2)))"
     else
          len1="82$(printf "%04x" $((dh_param_len/2)))"
     fi
     dh_param="30${len1}${dh_p}${dh_g}"

     # Make a SEQUENCE of the parameters SEQUENCE and the OID
     dh_param_len=22+${#dh_param}
     if [[ $dh_param_len -lt 256 ]]; then
          len1="$(printf "%02x" $((dh_param_len/2)))"
     elif [[ $dh_param_len -lt 512 ]]; then
          len1="81$(printf "%02x" $((dh_param_len/2)))"
     else
          len1="82$(printf "%04x" $((dh_param_len/2)))"
     fi
     dh_param="30${len1}06092A864886F70D010301${dh_param}"

     # Encapsulate public key, y, in a BIT STRING
     dh_y_len=${#dh_y}+2
     if [[ $dh_y_len -lt 256 ]]; then
          len1="$(printf "%02x" $((dh_y_len/2)))"
     elif [[ $dh_y_len -lt 512 ]]; then
          len1="81$(printf "%02x" $((dh_y_len/2)))"
     else
          len1="82$(printf "%04x" $((dh_y_len/2)))"
     fi
     dh_y="03${len1}00$dh_y"

     # Create the public key SEQUENCE
     i=${#dh_param}+${#dh_y}
     if [[ $i -lt 256 ]]; then
          len1="$(printf "%02x" $((i/2)))"
     elif [[ $i -lt 512 ]]; then
          len1="81$(printf "%02x" $((i/2)))"
     else
          len1="82$(printf "%04x" $((i/2)))"
     fi
     key_bitstring="30${len1}${dh_param}${dh_y}"
     key_bitstring="$(asciihex_to_binary "$key_bitstring" | $OPENSSL pkey -pubin -inform DER 2> $ERRFILE)"
     [[ -z "$key_bitstring" ]] && return 1
     tm_out "$key_bitstring"
     return 0
}

# arg1: name of file with socket reply
# arg2: true if entire server hello should be parsed
# return values: 0=no SSLv2 (reset)
#                1=no SSLv2 (plaintext reply like it happens with OLS webservers)
#                3=SSLv2 supported (in $TEMPDIR/$NODEIP.sslv2_sockets.dd is reply for further processing
#                  --> there could be checked whether ciphers e.g have been returned at all (or anything else)
#                4=looks like an STARTTLS 5xx message
#                6=socket couldn't be opened
#                7=strange reply we can't deal with
parse_sslv2_serverhello() {
     local ret v2_hello_ascii v2_hello_initbyte v2_hello_length
     local v2_hello_handshake v2_cert_type v2_hello_cert_length
     local v2_hello_cipherspec_length
     local -i certificate_len nr_ciphers_detected offset i
     local ret=3
     local parse_complete="false"
     # SSLv2 server hello:                                             in hex representation, see below
     # byte 1+2: length of server hello                          0123
     # 3:        04=Handshake message, server hello              45
     # 4:        session id hit or not (boolean: 00=false, this  67
     #           is the normal case)
     # 5:        certificate type, 01 = x509                     89
     # 6+7       version (00 02 = SSLv2)                         10-13
     # 8+9       certificate length                              14-17
     # 10+11     cipher spec length                              17-20
     # 12+13     connection id length
     # [certificate length] ==> certificate
     # [cipher spec length] ==> ciphers GOOD: HERE ARE ALL CIPHERS ALREADY!

     # Note: recent SSL/TLS stacks reply with a TLS alert on a SSLv2 client hello.
     # The TLS error message is different and could be used for fingerprinting.

     if [[ "$2" == "true" ]]; then
          parse_complete=true
     fi
     "$parse_complete" && echo "======================================" > $TMPFILE

     v2_hello_ascii=$(hexdump -v -e '16/1 "%02X"' $1)
     v2_hello_ascii="${v2_hello_ascii%%[!0-9A-F]*}"
     [[ "$DEBUG" -ge 5 ]] && echo "$v2_hello_ascii"
     if [[ -z "$v2_hello_ascii" ]]; then
          ret=0                                      # 1 line without any blanks: no server hello received
          debugme echo "server hello empty"
     else
          # now scrape two bytes out of the reply per byte
          v2_hello_initbyte="${v2_hello_ascii:0:1}"  # normally this belongs to the next, should be 8!
          v2_hello_length="${v2_hello_ascii:1:3}"    # + 0x8000 see above
          v2_hello_handshake="${v2_hello_ascii:4:2}"
          v2_cert_type="${v2_hello_ascii:8:2}"
          v2_hello_cert_length="${v2_hello_ascii:14:4}"
          v2_hello_cipherspec_length="${v2_hello_ascii:18:4}"

          V2_HELLO_CIPHERSPEC_LENGTH=$(printf "%d\n" "0x$v2_hello_cipherspec_length" 2>/dev/null)
          [[ $? -ne 0 ]] && ret=7

          if [[ "${v2_hello_ascii:0:2}" == "35" ]] && "$do_starttls"; then
               # this could be a 500/5xx for some weird reason where the STARTTLS handshake failed
               debugme echo "$(hex2ascii "$v2_hello_ascii")"
               ret=4
          elif [[ "${v2_hello_ascii:0:4}" == "1503" ]]; then
               # Cloudflare does this, OpenSSL 1.1.1 and picoTLS. With different alert messages
               # Just in case somebody's interested in the exact error, we deliver it ;-)
               debugme echo -n ">TLS< alert message discovered: ${v2_hello_ascii} "
               case "${v2_hello_ascii:10:2}" in
                    01) debugme echo "(01/warning: 0x"${v2_hello_ascii:12:2}"/$(tls_alert "${v2_hello_ascii:12:2}"))" ;;
                    02) debugme echo "(02/fatal: 0x"${v2_hello_ascii:12:2}"/$(tls_alert "${v2_hello_ascii:12:2}"))" ;;
                    *)  debugme echo "("${v2_hello_ascii:10:2}" : "${v2_hello_ascii:12:2}"))" ;;
               esac
               ret=0
          elif [[ $v2_hello_initbyte != "8" ]] || [[ $v2_hello_handshake != "04" ]]; then
               ret=1
               if [[ $DEBUG -ge 2 ]]; then
                    echo "no correct server hello"
                    echo "SSLv2 server init byte:    0x0$v2_hello_initbyte"
                    echo "SSLv2 hello handshake :    0x$v2_hello_handshake"
               fi
          fi

          if [[ $DEBUG -ge 3 ]]; then
               echo "SSLv2 server hello length: 0x0$v2_hello_length"
               echo "SSLv2 certificate type:    0x$v2_cert_type"
               echo "SSLv2 certificate length:  0x$v2_hello_cert_length"
               echo "SSLv2 cipher spec length:  0x$v2_hello_cipherspec_length"
          fi

          if "$parse_complete" && [[ 2*$(hex2dec "$v2_hello_length") -ne ${#v2_hello_ascii}-4 ]]; then
               ret=7
          fi
     fi

     "$parse_complete" || return $ret

     # not sure why we need this
     rm -f $HOSTCERT
     > $TEMPDIR/intermediatecerts.pem
     if [[ $ret -eq 3 ]]; then
          certificate_len=2*$(hex2dec "$v2_hello_cert_length")

          if [[ "$v2_cert_type" == "01" ]] && [[ "$v2_hello_cert_length" != "00" ]]; then
               asciihex_to_binary "${v2_hello_ascii:26:certificate_len}" | \
                    $OPENSSL x509 -inform DER -outform PEM -out $HOSTCERT 2>$ERRFILE
               if [[ $? -ne 0 ]]; then
                    debugme echo "Malformed certificate in ServerHello."
                    return 1
               fi
               get_pub_key_size
               echo "======================================" >> $TMPFILE
          fi

          # Output list of supported ciphers
          offset=$((certificate_len+26))
          nr_ciphers_detected=$((V2_HELLO_CIPHERSPEC_LENGTH / 3))
          for (( i=0 ; i<nr_ciphers_detected; i++ )); do
               echo "Supported cipher: x$(tolower "${v2_hello_ascii:offset:6}")" >> $TMPFILE
               offset=$((offset+6))
          done
          echo "======================================" >> $TMPFILE

          tmpfile_handle ${FUNCNAME[0]}.txt
     fi
     return $ret
}

# arg1: hash function
# arg2: key
# arg3: text
hmac() {
     local hash_fn="$1"
     local key="$2" text="$3" output
     local -i ret

     if [[ ! "$OSSL_NAME" =~ LibreSSL ]] && [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 3.0.0* ]]; then
          output="$(asciihex_to_binary "$text" | $OPENSSL mac -macopt digest:"${hash_fn/-/}" -macopt hexkey:"$key" HMAC 2>/dev/null)"
          ret=$?
          tm_out "$(strip_lf "$output")"
     else
          output="$(asciihex_to_binary "$text" | $OPENSSL dgst "$hash_fn" -mac HMAC -macopt hexkey:"$key" 2>/dev/null)"
          ret=$?
          tm_out "$(awk  '/=/ { print $2 }' <<< "$output")"
     fi
     return $ret
}

# arg1: hash function
# arg2: key
# arg3: transcript
# Compute the HMAC of the hash of the transcript
hmac-transcript() {
     local hash_fn="$1"
     local key="$2" transcript="$3" output
     local -i ret

     if [[ ! "$OSSL_NAME" =~ LibreSSL ]] && [[ $OSSL_VER_MAJOR.$OSSL_VER_MINOR == 3.0.0* ]]; then
          output="$(asciihex_to_binary "$transcript" | \
                    $OPENSSL dgst "$hash_fn" -binary 2>/dev/null | \
                    $OPENSSL mac -macopt digest:"${hash_fn/-/}" -macopt hexkey:"$key" HMAC 2>/dev/null)"
          ret=$?
          tm_out "$(toupper "$(strip_lf "$output")")"
     else
          output="$(asciihex_to_binary "$transcript" | \
                    $OPENSSL dgst "$hash_fn" -binary 2>/dev/null | \
                    $OPENSSL dgst "$hash_fn" -mac HMAC -macopt hexkey:"$key" 2>/dev/null)"
          ret=$?
          tm_out "$(toupper "$(awk  '/=/ { print $2 }' <<< "$output")")"
     fi
     return $ret
}

# arg1: hash function
# arg2: pseudorandom key (PRK)
# arg2: info
# arg3: length of output keying material in octets
# See RFC 5869, Section 2.3
hkdf-expand() {
     local hash_fn="$1"
     local prk="$2" info="$3" output=""
     local -i out_len="$4"
     local -i i n hash_len ret
     local counter
     local ti tim1 # T(i) and T(i-1)

     case "$hash_fn" in
          "-sha256") hash_len=32 ;;
          "-sha384") hash_len=48 ;;
          *) return 7
     esac

     n=$out_len/$hash_len
     [[ $((out_len%hash_len)) -ne 0 ]] && n+=1

     tim1=""
     for (( i=1; i <= n; i++ )); do
          counter="$(printf "%02X\n" $i)"
          ti="$(hmac "$hash_fn" "$prk" "$tim1$info$counter")"
          [[ $? -ne 0 ]] && return 7
          output+="$ti"
          tim1="$ti"
     done
     out_len=$((2*out_len))
     tm_out "${output:0:out_len}"
     return 0
}

# arg1: hash function
# arg2: secret
# arg3: label
# arg4: context
# arg5: length
# See RFC 8446, Section 7.1
hkdf-expand-label() {
     local hash_fn="$1"
     local secret="$2" label="$3"
     local context="$4"
     local -i length="$5"
     local hkdflabel hkdflabel_label hkdflabel_context
     local hkdflabel_length
     local -i len

     hkdflabel_length="$(printf "%04X\n" "$length")"
     if [[ "${TLS_SERVER_HELLO:8:2}" == 7F ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then
          # "544c5320312e332c20" = "TLS 1.3, "
          hkdflabel_label="544c5320312e332c20$label"
     else
          # "746c73313320" = "tls13 "
          hkdflabel_label="746c73313320$label"
     fi
     len=${#hkdflabel_label}/2
     hkdflabel_label="$(printf "%02X\n" "$len")$hkdflabel_label"
     len=${#context}/2
     hkdflabel_context="$(printf "%02X\n" "$len")$context"
     hkdflabel="$hkdflabel_length$hkdflabel_label$hkdflabel_context"

     hkdf-expand "$hash_fn" "$secret" "$hkdflabel" "$length"
     return $?
}

# arg1: hash function
# arg2: secret
# arg3: label
# arg4: ASCII-HEX of messages
# See RFC 8446, Section 7.1
derive-secret() {
     local hash_fn="$1"
     local secret="$2" label="$3" messages="$4"
     local hash_messages
     local -i hash_len retcode

     case "$hash_fn" in
          "-sha256") hash_len=32 ;;
          "-sha384") hash_len=48 ;;
          *) return 7
     esac

     hash_messages="$(asciihex_to_binary "$messages" | $OPENSSL dgst "$hash_fn" 2>/dev/null | awk  '/=/ { print $2 }')"
     hkdf-expand-label "$hash_fn" "$secret" "$label" "$hash_messages" "$hash_len"
     return $?
}

# arg1: hash function
# arg2: secret
# arg3: purpose ("key" or "iv")
# arg4: length of the key
# See RFC 8446, Section 7.3
derive-traffic-key() {
     local hash_fn="$1"
     local secret="$2" purpose="$3"
     local -i key_length="$4"
     local key

     key="$(hkdf-expand-label "$hash_fn" "$secret" "$purpose" "" "$key_length")"
     [[ $? -ne 0 ]] && return 7
     tm_out "$key"
     return 0
}

#arg1: TLS cipher
#arg2: First ClientHello, if response was a HelloRetryRequest
#arg3: HelloRetryRequest, if one was sent
#arg4: Final (or only) ClientHello
#arg5: ServerHello
create-initial-transcript() {
     local cipher="$1"
     local clienthello1="$2" hrr="$3" clienthello2="$4" serverhello="$5"
     local hash_clienthello1 msg_transcript

     if [[ -n "$hrr" ]] && [[ "${serverhello:8:4}" == 7F12 ]]; then
          msg_transcript="$clienthello1$hrr$clienthello2$serverhello"
     elif [[ -n "$hrr" ]]; then
          if [[ "$cipher" == *SHA256 ]]; then
               hash_fn="-sha256"
               hash_len=32
          elif [[ "$cipher" == *SHA384 ]]; then
               hash_fn="-sha384"
               hash_len=48
          else
               return 1
          fi
          hash_clienthello1="$(asciihex_to_binary "$clienthello1" | $OPENSSL dgst "$hash_fn" 2>/dev/null | awk  '/=/ { print $2 }')"
          msg_transcript="FE0000$(printf "%02x" $((${#hash_clienthello1}/2)))$hash_clienthello1$hrr$clienthello2$serverhello"
     else
          msg_transcript="$clienthello2$serverhello"
     fi
     tm_out "$msg_transcript"
     return 0
}

#arg1: TLS cipher
#arg2: file containing cipher name, public key, and private key
derive-handshake-secret() {
     local cipher="$1"
     local tmpfile="$2"
     local -i retcode
     local hash_fn
     local pub_file priv_file tmpfile
     local early_secret derived_secret shared_secret handshake_secret

     "$HAS_PKUTIL" || return 1

     if [[ "$cipher" == *SHA256 ]]; then
          hash_fn="-sha256"
     elif [[ "$cipher" == *SHA384 ]]; then
          hash_fn="-sha384"
     else
          return 1
     fi

     pub_file="$(mktemp "$TEMPDIR/pubkey.XXXXXX")" || return 7
     awk '/-----BEGIN PUBLIC KEY/,/-----END PUBLIC KEY/ { print $0 }' \
          "$tmpfile" > "$pub_file"
     [[ ! -s "$pub_file" ]] && return 1

     priv_file="$(mktemp "$TEMPDIR/privkey.XXXXXX")" || return 7
     if grep -q "\-\-\-\-\-BEGIN EC PARAMETERS" "$tmpfile"; then
          awk '/-----BEGIN EC PARAMETERS/,/-----END EC PRIVATE KEY/ { print $0 }' \
               "$tmpfile" > "$priv_file"
     else
          awk '/-----BEGIN PRIVATE KEY/,/-----END PRIVATE KEY/ { print $0 }' \
               "$tmpfile" > "$priv_file"
     fi
     [[ ! -s "$priv_file" ]] && return 1

     # early_secret="$(hmac "$hash_fn" "000...000" "000...000")"
     case "$hash_fn" in
          "-sha256") early_secret="33ad0a1c607ec03b09e6cd9893680ce210adf300aa1f2660e1b22e10f170f92a"
                     if [[ "${TLS_SERVER_HELLO:8:2}" == 7F ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then
                          # "6465726976656420736563726574" = "derived secret"
                          # derived_secret="$(derive-secret "$hash_fn" "$early_secret" "6465726976656420736563726574" "")"
                          derived_secret="c1c0c36bf8fb1d1afa949fbd360e71af69a6244a4c2eaef5bbbb6442a7277d2c"
                     else
                          # "64657269766564" = "derived"
                          # derived_secret="$(derive-secret "$hash_fn" "$early_secret" "64657269766564" "")"
                          derived_secret="6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611ba"
                     fi
                     ;;
          "-sha384") early_secret="7ee8206f5570023e6dc7519eb1073bc4e791ad37b5c382aa10ba18e2357e716971f9362f2c2fe2a76bfd78dfec4ea9b5"
                     if [[ "${TLS_SERVER_HELLO:8:2}" == 7F ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then
                           # "6465726976656420736563726574" = "derived secret"
                           # derived_secret="$(derive-secret "$hash_fn" "$early_secret" "6465726976656420736563726574" "")"
                          derived_secret="54c80fa05ee9e0532ce3db8ddeca37a0365683bcd3b27bdc88d2b9fdc115ca4ebc8edc1f0b72a6a0861e803fc34761ef"
                     else
                          # "64657269766564" = "derived"
                          # derived_secret="$(derive-secret "$hash_fn" "$early_secret" "64657269766564" "")"
                          derived_secret="1591dac5cbbf0330a4a84de9c753330e92d01f0a88214b4464972fd668049e93e52f2b16fad922fdc0584478428f282b"
                     fi
                     ;;
     esac

     shared_secret="$($OPENSSL pkeyutl -derive -inkey "$priv_file" -peerkey "$pub_file" 2>/dev/null | hexdump -v -e '16/1 "%02X"')"
     rm "$pub_file" "$priv_file"

     # For draft 18 use $early_secret rather than $derived_secret.
     if [[ "${TLS_SERVER_HELLO:8:4}" == "7F12" ]]; then
          handshake_secret="$(hmac "$hash_fn" "$early_secret" "${shared_secret%%[!0-9A-F]*}")"
     else
          handshake_secret="$(hmac "$hash_fn" "$derived_secret" "${shared_secret%%[!0-9A-F]*}")"
     fi
     [[ $? -ne 0 ]] && return 7

     tm_out "$handshake_secret"
     return 0
}

# arg1: TLS cipher
# arg2: handshake secret
# arg3: transcipt
# arg4: "client" or "server"
derive-handshake-traffic-keys() {
     local cipher="$1" handshake_secret="$2" transcript="$3"
     local sender="$4"
     local hash_fn
     local -i hash_len key_len
     local handshake_traffic_secret label key iv finished="0000"

     if [[ "$cipher" == *SHA256 ]]; then
          hash_fn="-sha256"
          hash_len=32
     elif [[ "$cipher" == *SHA384 ]]; then
          hash_fn="-sha384"
          hash_len=48
     else
          return 1
     fi
     if [[ "$cipher" == *AES_128* ]]; then
          key_len=16
     elif ( [[ "$cipher" == *AES_256* ]] || [[ "$cipher" == *CHACHA20_POLY1305* ]] ); then
          key_len=32
     else
          return 1
     fi

     if [[ "${TLS_SERVER_HELLO:8:2}" == 7F ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then
          if [[ "$sender" == server ]]; then
               # "7365727665722068616e647368616b65207472616666696320736563726574" = "server handshake traffic secret"
               label="7365727665722068616e647368616b65207472616666696320736563726574"
          else
               # "636c69656e742068616e647368616b65207472616666696320736563726574" = "client handshake traffic secret"
               label="636c69656e742068616e647368616b65207472616666696320736563726574"
          fi
     elif [[ "$sender" == server ]]; then
          # "732068732074726166666963" = "s hs traffic"
          label="732068732074726166666963"
     else
          # "632068732074726166666963" = "c hs traffic"
          label="632068732074726166666963"
     fi
     handshake_traffic_secret="$(derive-secret "$hash_fn" "$handshake_secret" "$label" "$transcript")"
     [[ $? -ne 0 ]] && return 7

     # "6b6579" = "key"
     key="$(derive-traffic-key "$hash_fn" "$handshake_traffic_secret" "6b6579" "$key_len")"
     [[ $? -ne 0 ]] && return 1
     # "6976" = "iv"
     iv="$(derive-traffic-key "$hash_fn" "$handshake_traffic_secret" "6976" "12")"
     [[ $? -ne 0 ]] && return 1
     if [[ $DEBUG -ge 1 ]] || [[ "$sender" == client ]]; then
          # "66696e6973686564" = "finished"
          finished="$(derive-traffic-key "$hash_fn" "$handshake_traffic_secret" "66696e6973686564" "$hash_len")"
          [[ $? -ne 0 ]] && return 1
     fi
     tm_out "$key $iv $finished"
}

#arg1: TLS cipher
#arg2: handshake secret
derive-master-secret() {
     local cipher="$1"
     local handshake_secret="$2"
     local -i retcode
     local hash_fn
     local derived_secret zeros master_secret

     if [[ "$cipher" == *SHA256 ]]; then
          hash_fn="-sha256"
          zeros="0000000000000000000000000000000000000000000000000000000000000000"
     elif [[ "$cipher" == *SHA384 ]]; then
          hash_fn="-sha384"
          zeros="000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
     else
          return 1
     fi

     if [[ "${TLS_SERVER_HELLO:8:4}" == 7F12 ]]; then
          derived_secret="$handshake_secret"
     elif [[ "${TLS_SERVER_HELLO:8:2}" == 7F ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then
          derived_secret="$(derive-secret "$hash_fn" "$handshake_secret" "6465726976656420736563726574" "")"
     else
          derived_secret="$(derive-secret "$hash_fn" "$handshake_secret" "64657269766564" "")"
     fi
     master_secret="$(hmac "$hash_fn" "$derived_secret" "$zeros")"
     [[ $? -ne 0 ]] && return 7

     tm_out "$master_secret"
     return 0
}

# arg1: TLS cipher
# arg2: master secret
# arg3: transcipt
# arg4: "client" or "server"
derive-application-traffic-keys() {
     local cipher="$1" master_secret="$2" transcript="$3"
     local sender="$4"
     local hash_fn
     local -i key_len
     local application_traffic_secret_0 label key iv

     if [[ "$cipher" == *SHA256 ]]; then
          hash_fn="-sha256"
     elif [[ "$cipher" == *SHA384 ]]; then
          hash_fn="-sha384"
     else
          return 1
     fi
     if [[ "$cipher" == *AES_128* ]]; then
          key_len=16
     elif ( [[ "$cipher" == *AES_256* ]] || [[ "$cipher" == *CHACHA20_POLY1305* ]] ); then
          key_len=32
     else
          return 1
     fi

     if [[ "${TLS_SERVER_HELLO:8:2}" == 7F ]] && [[ 0x${TLS_SERVER_HELLO:10:2} -lt 0x14 ]]; then
          if [[ "$sender" == server ]]; then
               # "736572766572206170706c69636174696f6e207472616666696320736563726574" = "server application traffic secret"
               label="736572766572206170706c69636174696f6e207472616666696320736563726574"
          else
               # "636c69656e74206170706c69636174696f6e207472616666696320736563726574" = "client application traffic secret"
               label="636c69656e74206170706c69636174696f6e207472616666696320736563726574"
          fi
     elif [[ "$sender" == server ]]; then
          # "732061702074726166666963" = "s hs traffic"
          label="732061702074726166666963"
     else
          # "632061702074726166666963" = "c hs traffic"
          label="632061702074726166666963"
     fi
     application_traffic_secret_0="$(derive-secret "$hash_fn" "$master_secret" "$label" "$transcript")"
     [[ $? -ne 0 ]] && return 7

     # "6b6579" = "key"
     key="$(derive-traffic-key "$hash_fn" "$application_traffic_secret_0" "6b6579" "$key_len")"
     [[ $? -ne 0 ]] && return 1
     # "6976" = "iv"
     iv="$(derive-traffic-key "$hash_fn" "$application_traffic_secret_0" "6976" "12")"
     [[ $? -ne 0 ]] && return 1
     tm_out "$key $iv"
}

# See RFC 8439, Section 2.1
chacha20_Qround() {
     local -i a="0x$1"
     local -i b="0x$2"
     local -i c="0x$3"
     local -i d="0x$4"
     local -i x y

     a=$(((a+b) & 0xffffffff))
     d=$((d^a))
     # rotate d left 16 bits
     x=$((d & 0xffff0000))
     x=$((x >> 16))
     y=$((d & 0x0000ffff))
     y=$((y << 16))
     d=$((x | y))

     c=$(((c+d) & 0xffffffff))
     b=$((b^c))
     # rotate b left 12 bits
     x=$((b & 0xfff00000))
     x=$((x >> 20))
     y=$((b & 0x000fffff))
     y=$((y << 12))
     b=$((x | y))

     a=$(((a+b) & 0xffffffff))
     d=$((d^a))
     # rotate d left 8 bits
     x=$((d & 0xff000000))
     x=$((x >> 24))
     y=$((d & 0x00ffffff))
     y=$((y << 8))
     d=$((x | y))

     c=$(((c+d) & 0xffffffff))
     b=$((b^c))
     # rotate b left 7 bits
     x=$((b & 0xfe000000))
     x=$((x >> 25))
     y=$((b & 0x01ffffff))
     y=$((y << 7))
     b=$((x | y))

     tm_out "$(printf "%X" $a) $(printf "%X" $b) $(printf "%X" $c) $(printf "%X" $d)"
     return 0
}

# See RFC 8439, Section 2.3.1
chacha20_inner_block() {
     local s0="$1" s1="$2" s2="$3" s3="$4"
     local s4="$5" s5="$6" s6="$7" s7="$8"
     local s8="$9" s9="${10}" s10="${11}" s11="${12}"
     local s12="${13}" s13="${14}" s14="${15}" s15="${16}"
     local res

     res="$(chacha20_Qround "$s0" "$s4" "$s8" "$s12")"
     read -r s0 s4 s8 s12 <<< "$res"
     res="$(chacha20_Qround "$s1" "$s5" "$s9" "$s13")"
     read -r s1 s5 s9 s13 <<< "$res"
     res="$(chacha20_Qround "$s2" "$s6" "$s10" "$s14")"
     read -r s2 s6 s10 s14 <<< "$res"
     res="$(chacha20_Qround "$s3" "$s7" "$s11" "$s15")"
     read -r s3 s7 s11 s15 <<< "$res"
     res="$(chacha20_Qround "$s0" "$s5" "$s10" "$s15")"
     read -r s0 s5 s10 s15 <<< "$res"
     res="$(chacha20_Qround "$s1" "$s6" "$s11" "$s12")"
     read -r s1 s6 s11 s12 <<< "$res"
     res="$(chacha20_Qround "$s2" "$s7" "$s8" "$s13")"
     read -r s2 s7 s8 s13 <<< "$res"
     res="$(chacha20_Qround "$s3" "$s4" "$s9" "$s14")"
     read -r s3 s4 s9 s14 <<< "$res"

     tm_out "$s0 $s1 $s2 $s3 $s4 $s5 $s6 $s7 $s8 $s9 $s10 $s11 $s12 $s13 $s14 $s15"
     return 0
}

# See RFC 8439, Sections 2.3 and 2.3.1
chacha20_block() {
     local key="$1"
     local counter="$2"
     local nonce="$3"
     local s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15
     local ws0 ws1 ws2 ws3 ws4 ws5 ws6 ws7 ws8 ws9 ws10 ws11 ws12 ws13 ws14 ws15
     local working_state
     local -i i

     # create the state variable
     s0="61707865"; s1="3320646e"; s2="79622d32"; s3="6b206574"
     s4="${key:6:2}${key:4:2}${key:2:2}${key:0:2}"
     s5="${key:14:2}${key:12:2}${key:10:2}${key:8:2}"
     s6="${key:22:2}${key:20:2}${key:18:2}${key:16:2}"
     s7="${key:30:2}${key:28:2}${key:26:2}${key:24:2}"
     s8="${key:38:2}${key:36:2}${key:34:2}${key:32:2}"
     s9="${key:46:2}${key:44:2}${key:42:2}${key:40:2}"
     s10="${key:54:2}${key:52:2}${key:50:2}${key:48:2}"
     s11="${key:62:2}${key:60:2}${key:58:2}${key:56:2}"
     s12="$counter"
     s13="${nonce:6:2}${nonce:4:2}${nonce:2:2}${nonce:0:2}"
     s14="${nonce:14:2}${nonce:12:2}${nonce:10:2}${nonce:8:2}"
     s15="${nonce:22:2}${nonce:20:2}${nonce:18:2}${nonce:16:2}"

     # Initialize working_state to state
     working_state="$s0 $s1 $s2 $s3 $s4 $s5 $s6 $s7 $s8 $s9 $s10 $s11 $s12 $s13 $s14 $s15"

     # compute the 20 rounds (10 calls to inner block function, each of which
     # performs 8 quarter rounds).
     for (( i=0 ; i < 10; i++ )); do
          working_state="$(chacha20_inner_block $working_state)"
     done
     read -r ws0 ws1 ws2 ws3 ws4 ws5 ws6 ws7 ws8 ws9 ws10 ws11 ws12 ws13 ws14 ws15 <<< "$working_state"

     # Add working state to state
     s0="$(printf "%08X" $(((0x$s0+0x$ws0) & 0xffffffff)))"
     s1="$(printf "%08X" $(((0x$s1+0x$ws1) & 0xffffffff)))"
     s2="$(printf "%08X" $(((0x$s2+0x$ws2) & 0xffffffff)))"
     s3="$(printf "%08X" $(((0x$s3+0x$ws3) & 0xffffffff)))"
     s4="$(printf "%08X" $(((0x$s4+0x$ws4) & 0xffffffff)))"
     s5="$(printf "%08X" $(((0x$s5+0x$ws5) & 0xffffffff)))"
     s6="$(printf "%08X" $(((0x$s6+0x$ws6) & 0xffffffff)))"
     s7="$(printf "%08X" $(((0x$s7+0x$ws7) & 0xffffffff)))"
     s8="$(printf "%08X" $(((0x$s8+0x$ws8) & 0xffffffff)))"
     s9="$(printf "%08X" $(((0x$s9+0x$ws9) & 0xffffffff)))"
     s10="$(printf "%08X" $(((0x$s10+0x$ws10) & 0xffffffff)))"
     s11="$(printf "%08X" $(((0x$s11+0x$ws11) & 0xffffffff)))"
     s12="$(printf "%08X" $(((0x$s12+0x$ws12) & 0xffffffff)))"
     s13="$(printf "%08X" $(((0x$s13+0x$ws13) & 0xffffffff)))"
     s14="$(printf "%08X" $(((0x$s14+0x$ws14) & 0xffffffff)))"
     s15="$(printf "%08X" $(((0x$s15+0x$ws15) & 0xffffffff)))"

     # serialize the state
     s0="${s0:6:2}${s0:4:2}${s0:2:2}${s0:0:2}"
     s1="${s1:6:2}${s1:4:2}${s1:2:2}${s1:0:2}"
     s2="${s2:6:2}${s2:4:2}${s2:2:2}${s2:0:2}"
     s3="${s3:6:2}${s3:4:2}${s3:2:2}${s3:0:2}"
     s4="${s4:6:2}${s4:4:2}${s4:2:2}${s4:0:2}"
     s5="${s5:6:2}${s5:4:2}${s5:2:2}${s5:0:2}"
     s6="${s6:6:2}${s6:4:2}${s6:2:2}${s6:0:2}"
     s7="${s7:6:2}${s7:4:2}${s7:2:2}${s7:0:2}"
     s8="${s8:6:2}${s8:4:2}${s8:2:2}${s8:0:2}"
     s9="${s9:6:2}${s9:4:2}${s9:2:2}${s9:0:2}"
     s10="${s10:6:2}${s10:4:2}${s10:2:2}${s10:0:2}"
     s11="${s11:6:2}${s11:4:2}${s11:2:2}${s11:0:2}"
     s12="${s12:6:2}${s12:4:2}${s12:2:2}${s12:0:2}"
     s13="${s13:6:2}${s13:4:2}${s13:2:2}${s13:0:2}"
     s14="${s14:6:2}${s14:4:2}${s14:2:2}${s14:0:2}"
     s15="${s15:6:2}${s15:4:2}${s15:2:2}${s15:0:2}"

     tm_out "$s0$s1$s2$s3$s4$s5$s6$s7$s8$s9$s10$s11$s12$s13$s14$s15"
     return 0
}

# See RFC 8439, Section 2.4
chacha20() {
     local key="$1"
     local -i counter=1
     local nonce="$2"
     local ciphertext="$3"
     local -i i ciphertext_len num_blocks mod_check
     local -i i1 i2 i3 i4 i5 i6 i7 i8 i9 i10 i11 i12 i13 i14 i15 i16
     local keystream plaintext=""

     if "$HAS_CHACHA20"; then
          plaintext="$(asciihex_to_binary "$ciphertext" | \
                       $OPENSSL enc -chacha20 -K "$key" -iv "01000000$nonce" 2>/dev/null | hexdump -v -e '16/1 "%02X"')"
          tm_out "$(strip_spaces "$plaintext")"
          return 0
     fi

     ciphertext_len=${#ciphertext}
     num_blocks=$ciphertext_len/128

     for (( i=0; i < num_blocks; i++)); do
          i1=$((128*i)); i2=$((i1+8)); i3=$((i1+16)); i4=$((i1+24)); i5=$((i1+32)); i6=$((i1+40)); i7=$((i1+48)); i8=$((i1+56))
          i9=$((i1+64)); i10=$((i1+72)); i11=$((i1+80)); i12=$((i1+88)); i13=$((i1+96)); i14=$((i1+104)); i15=$((i1+112)); i16=$((i1+120))
          keystream="$(chacha20_block "$key" "$(printf "%08X" $counter)" "$nonce")"
          plaintext+="$(printf "%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X" \
               "$((0x${ciphertext:i1:8} ^ 0x${keystream:0:8}))" \
               "$((0x${ciphertext:i2:8} ^ 0x${keystream:8:8}))" \
               "$((0x${ciphertext:i3:8} ^ 0x${keystream:16:8}))" \
               "$((0x${ciphertext:i4:8} ^ 0x${keystream:24:8}))" \
               "$((0x${ciphertext:i5:8} ^ 0x${keystream:32:8}))" \
               "$((0x${ciphertext:i6:8} ^ 0x${keystream:40:8}))" \
               "$((0x${ciphertext:i7:8} ^ 0x${keystream:48:8}))" \
               "$((0x${ciphertext:i8:8} ^ 0x${keystream:56:8}))" \
               "$((0x${ciphertext:i9:8} ^ 0x${keystream:64:8}))" \
               "$((0x${ciphertext:i10:8} ^ 0x${keystream:72:8}))" \
               "$((0x${ciphertext:i11:8} ^ 0x${keystream:80:8}))" \
               "$((0x${ciphertext:i12:8} ^ 0x${keystream:88:8}))" \
               "$((0x${ciphertext:i13:8} ^ 0x${keystream:96:8}))" \
               "$((0x${ciphertext:i14:8} ^ 0x${keystream:104:8}))" \
               "$((0x${ciphertext:i15:8} ^ 0x${keystream:112:8}))" \
               "$((0x${ciphertext:i16:8} ^ 0x${keystream:120:8}))")"
          counter+=1
     done

     mod_check=$ciphertext_len%128
     if [[ $mod_check -ne 0 ]]; then
          keystream="$(chacha20_block "$key" "$(printf "%08X" $counter)" "$nonce")"
          i1=$((128*num_blocks))
          for (( i=0; i < mod_check; i+=2 )); do
               plaintext+="$(printf "%02X" "$((0x${ciphertext:i1:2} ^ 0x${keystream:i:2}))")"
               i1+=2
          done
     fi
     tm_out "$plaintext"
     return 0
}

# Implement U8to32 from https://github.com/floodyberry/poly1305-donna/blob/master/poly1305-donna-32.h
# Used to decode value encoded as 32-bit little-endian integer
u8to32() {
     local p="$1"

     tm_out "0x${p:6:2}${p:4:2}${p:2:2}${p:0:2}"
     return 0
}

# Implement U32to8 from https://github.com/floodyberry/poly1305-donna/blob/master/poly1305-donna-32.h
# Used to encode value as 32-bit little-endian integer
u32to8() {
     local -i v="$1"
     local p

     v=$((v & 0xffffffff))
     p="$(printf "%08X" $v)"
     tm_out "${p:6:2}${p:4:2}${p:2:2}${p:0:2}"
     return 0
}

# Used to encode value as 64-bit little-endian integer
u64to8() {
     local -i v="$1"
     local p

     p="$(printf "%016X" "$v")"
     tm_out "${p:14:2}${p:12:2}${p:10:2}${p:8:2}${p:6:2}${p:4:2}${p:2:2}${p:0:2}"
     return 0
}


# arg1: 32-byte key
# arg2: message to be authenticated
# See RFC 8439, Section 2.5
# Implementation based on https://github.com/floodyberry/poly1305-donna
poly1305_mac() {
     local key="$1" nonce="$2" ciphertext="$3" aad="$4"
     local mac_key msg
     local -i ciphertext_len aad_len
     local -i bytes
     local -i r0 r1 r2 r3 r4
     local -i h0=0 h1=0 h2=0 h3=0 h4=0
     local -i pad0 pad1 pad2 pad3
     local -i s1 s2 s3 s4
     local -i d0 d1 d2 d3 d4
     local -i g0 g1 g2 g3 g4
     local -i i c f blocksize hibit

     # poly1305_key_gen - RFC 8439, Section 2.6
     # The MAC key is actually just the first 64 characters (32 bytes) of the
     # output of the chacha20_block function. However, there is no need to
     # truncate the key, since the code below will ignore all but the first
     # 64 characters.
     mac_key="$(chacha20_block "$key" "00000000" "$nonce")"

     # Construct message to be authenticated. RFC 8439, Section 2.8
     msg="$aad"
     aad_len=$((${#aad}/2))
     bytes=$(( aad_len % 16 ))
     if [[ $bytes -ne 0 ]]; then
          for (( i=bytes; i < 16; i++ )); do
               msg+="00"
          done
     fi
     msg+="$ciphertext"
     ciphertext_len=$((${#ciphertext}/2))
     bytes=$(( ciphertext_len % 16 ))
     if [[ $bytes -ne 0 ]]; then
          for (( i=bytes; i < 16; i++ )); do
               msg+="00"
          done
     fi
     msg+="$(u64to8 $aad_len)$(u64to8 $ciphertext_len)"
     bytes="${#msg}"

     # poly1305_init
     r0=$(( $(u8to32 "${mac_key:0:8}") & 0x3ffffff ))
     r1=$(( ($(u8to32 "${mac_key:6:8}") >> 2) & 0x3ffff03 ))
     r2=$(( ($(u8to32 "${mac_key:12:8}") >> 4) & 0x3ffc0ff ))
     r3=$(( ($(u8to32 "${mac_key:18:8}") >> 6) & 0x3f03fff ))
     r4=$(( ($(u8to32 "${mac_key:24:8}") >> 8) & 0x00fffff ))

     s1=$((r1*5))
     s2=$((r2*5))
     s3=$((r3*5))
     s4=$((r4*5))

     pad0=$(u8to32 "${mac_key:32:8}")
     pad1=$(u8to32 "${mac_key:40:8}")
     pad2=$(u8to32 "${mac_key:48:8}")
     pad3=$(u8to32 "${mac_key:56:8}")

     # poly1305_update
     for (( 1 ; bytes > 0; bytes=bytes-blocksize )); do
          if [[ $bytes -ge 32 ]]; then
               blocksize=32
               hibit=0x1000000
          else
               blocksize=$bytes
               hibit=0
               msg+="01"
               for (( i=bytes+2; i < 32; i+=2 )); do
                    msg+="00"
               done
          fi
          h0+=$(( $(u8to32 "${msg:0:8}") & 0x3ffffff ))
          h1+=$(( ($(u8to32 "${msg:6:8}") >> 2) & 0x3ffffff ))
          h2+=$(( ($(u8to32 "${msg:12:8}") >> 4) & 0x3ffffff ))
          h3+=$(( ($(u8to32 "${msg:18:8}") >> 6) & 0x3ffffff ))
          h4+=$(( (($(u8to32 "${msg:24:8}") >> 8) & 0xffffff) | hibit ))

          d0=$(( h0*r0 + h1*s4 + h2*s3 + h3*s2 + h4*s1 ))
          d1=$(( h0*r1 + h1*r0 + h2*s4 + h3*s3 + h4*s2 ))
          d2=$(( h0*r2 + h1*r1 + h2*r0 + h3*s4 + h4*s3 ))
          d3=$(( h0*r3 + h1*r2 + h2*r1 + h3*r0 + h4*s4 ))
          d4=$(( h0*r4 + h1*r3 + h2*r2 + h3*r1 + h4*r0 ))

          c=$(( (d0 >> 26) & 0x3fffffffff )); h0=$(( d0 & 0x3ffffff ))
          d1+=$c; c=$(( (d1 >> 26) & 0x3fffffffff )); h1=$(( d1 & 0x3ffffff ))
          d2+=$c; c=$(( (d2 >> 26) & 0x3fffffffff )); h2=$(( d2 & 0x3ffffff ))
          d3+=$c; c=$(( (d3 >> 26) & 0x3fffffffff )); h3=$(( d3 & 0x3ffffff ))
          d4+=$c; c=$(( (d4 >> 26) & 0x3fffffffff )); h4=$(( d4 & 0x3ffffff ))
          h0+=$((c*5)); c=$(( (h0 >> 26) & 0x3fffffffff )); h0=$(( h0 & 0x3ffffff ))
          h1+=$c

          msg="${msg:32}"
     done

     # poly1305_finish
     c=$(( (h0 >> 26) & 0x3f )); h1=$(( h1 & 0x3ffffff ))
     h2+=$c; c=$(( (h2 >> 26) & 0x3f )); h2=$(( h2 & 0x3ffffff ))
     h3+=$c; c=$(( (h3 >> 26) & 0x3f )); h3=$(( h3 & 0x3ffffff ))
     h4+=$c; c=$(( (h4 >> 26) & 0x3f )); h4=$(( h4 & 0x3ffffff ))
     h0+=$((c*5)); c=$(( (h0 >> 26) & 0x3f )); h0=$(( h0 & 0x3ffffff ))
     h1+=$c

     g0=$((h0+5)); c=$(( (g0 >> 26) & 0x3f )); g0=$(( g0 & 0x3ffffff ))
     g1=$((h1+c)); c=$(( (g1 >> 26) & 0x3f )); g1=$(( g1 & 0x3ffffff ))
     g2=$((h2+c)); c=$(( (g2 >> 26) & 0x3f )); g2=$(( g2 & 0x3ffffff ))
     g3=$((h3+c)); c=$(( (g3 >> 26) & 0x3f )); g3=$(( g3 & 0x3ffffff ))
     g4=$((h4+c-0x4000000))

     if [[ $((g4 & 0x8000000000000000)) -eq 0 ]]; then
          h0=$g0; h1=$g1; h2=$g2; h3=$g3; h4=$g4
     fi
     h0=$(( ( h0 | (h1 << 26)) & 0xffffffff))
     h1=$(( ((h1 >> 6) | (h2 << 20)) & 0xffffffff))
     h2=$(( ((h2 >> 12) | (h3 << 14)) & 0xffffffff))
     h3=$(( ((h3 >> 18) | (h4 <<  8)) & 0xffffffff))

     f=$(( h0+pad0 )); h0=$f
     f=$(( h1+pad1+(f>>32) )); h1=$f
     f=$(( h2+pad2+(f>>32) )); h2=$f
     f=$(( h3+pad3+(f>>32) )); h3=$f

     tm_out "$(u32to8 $h0)$(u32to8 $h1)$(u32to8 $h2)$(u32to8 $h3)"
     return 0
}

# arg1: key
# arg2: nonce (must be 96 bits in length)
# arg3: ciphertext
# arg4: additional authenticated data
# arg5: expected tag
# arg6: true if authentication tag should be checked. false otherwise.
chacha20_aead_decrypt() {
     local key="$1" nonce="$2" ciphertext="$3" aad="$4" expected_tag="$(toupper "$5")"
     local compute_tag="$6"
     local plaintext computed_tag

     plaintext="$(chacha20 "$key" "$nonce" "$ciphertext")"
     [[ $? -ne 0 ]] && return 7

     if "$compute_tag"; then
          computed_tag="$(poly1305_mac "$key" "$nonce" "$ciphertext" "$aad")"
          [[ $? -ne 0 ]] && return 7
          [[ "$computed_tag" == $expected_tag ]] || return 7
     fi

     tm_out "$plaintext"
     return 0
}

# arg1: key
# arg2: nonce (must be 96 bits in length)
# arg3: plaintext
# arg4: additional authenticated data
chacha20_aead_encrypt() {
     local key="$1" nonce="$2" plaintext="$3" aad="$4"
     local ciphertext computed_tag

     ciphertext="$(chacha20 "$key" "$nonce" "$plaintext")"
     [[ $? -ne 0 ]] && return 7

     computed_tag="$(poly1305_mac "$key" "$nonce" "$ciphertext" "$aad")"
     [[ $? -ne 0 ]] && return 7

     tm_out "$ciphertext $computed_tag"
     return 0
}

# arg1: nonce (must be 96 bits)
# arg2: number of blocks needed for plaintext/ciphertext
# Generate the sequence of counter blocks, which are to be encrypted and then
# XORed with either the plaintext or the ciphertext.
# See Section 6.1, Section 6.2, and Appendix A.3 of NIST SP 800-38C and
# Section 5.3 of RFC 5116.
generate-ccm-counter-blocks() {
     local ctr="02${1}000000" ctr_msb ctr_lsb1
     local -i i ctr_lsb n="$2"

     ctr_msb="${ctr:0:24}"
     ctr_lsb=0x${ctr:24:8}

     for (( i=0; i <= n; i+=1 )); do
          ctr_lsb1="$(printf "%08X" "$ctr_lsb")"
          printf "\x${ctr_msb:0:2}\x${ctr_msb:2:2}\x${ctr_msb:4:2}\x${ctr_msb:6:2}\x${ctr_msb:8:2}\x${ctr_msb:10:2}\x${ctr_msb:12:2}\x${ctr_msb:14:2}\x${ctr_msb:16:2}\x${ctr_msb:18:2}\x${ctr_msb:20:2}\x${ctr_msb:22:2}\x${ctr_lsb1:0:2}\x${ctr_lsb1:2:2}\x${ctr_lsb1:4:2}\x${ctr_lsb1:6:2}"
          ctr_lsb+=1
     done
     return 0
}

# arg1: an OpenSSL ecb cipher (e.g., -aes-128-ecb)
# arg2: key
# arg3: iv (must be 96 bits in length)
# arg4: additional authenticated data
# arg5: plaintext
# arg6: tag length (must be 16 or 32)
# Compute the CCM authentication tag
ccm-compute-tag() {
     local cipher="$1" key="$2" iv="$3" aad="$4" plaintext="$5"
     local -i tag_len="$6"
     local b tag
     local -i i aad_len plaintext_len final_block_len nr_blocks

     aad_len=$((${#aad}/2))
     plaintext_len=$((${#plaintext}/2))

     # Apply the formatting function to create b=B0B1B2... as in
     # Appendix A.2 of NIST SP 800-38C.

     # The first block consists of the flags, nonce, and length of plaintext
     # See Section 5.3 of RFC 5116 for value of q.
     if [[ $aad_len -ne 0 ]]; then
          if [[ $tag_len -eq 16 ]]; then
               b="5A${iv}$(printf "%06X" $plaintext_len)"
          else
               b="7A${iv}$(printf "%06X" $plaintext_len)"
          fi
     elif [[ $tag_len -eq 16 ]]; then
          b="1A${iv}$(printf "%06X" $plaintext_len)"
     else
          b="3A${iv}$(printf "%06X" $plaintext_len)"
     fi

     # Next comes any additional authenticated data
     if [[ $aad_len -ne 0 ]]; then
          if [[ $aad_len -lt 0xFF00 ]]; then
               b+="$(printf "%04X" $aad_len)$aad"
               final_block_len=$(( (aad_len+2) % 16 ))
          elif [[ $aad_len -lt 0x100000000 ]]; then
               b+="FFFE$(printf "%08X" $aad_len)$aad"
               final_block_len=$(( (aad_len+6) % 16 ))
          else
               # AES-CCM supports lengths up to 2^64, but there doesn't
               # seem to be any reason to try to support such lengths.
               return 7
          fi
          # Add padding to complete block
          if [[ $final_block_len -ne 0 ]]; then
               for (( i=final_block_len; i < 16; i++ )); do
                    b+="00"
               done
          fi
     fi

     # Finally add the plaintext and any padding needed to complete block
     b+="$plaintext"
     final_block_len=$((plaintext_len % 16))
     if [[ $final_block_len -ne 0 ]]; then
          for (( i=final_block_len; i < 16; i++ )); do
               b+="00"
          done
     fi

     # Compute the authentication tag as described in
     # Sections 6.1 and 6.2 of NIST SP 800-38C.
     nr_blocks=$((${#b}/32))
     tag="${b:0:32}"
     for (( i=0; i < nr_blocks; i++ )); do
          # XOR current block with previous block and then encrypt
          [[ $i -ne 0 ]] &&
               tag="$(printf "%08X%08X%08X%08X" "$((0x${b:0:8} ^ 0x${tag:0:8}))" "$((0x${b:8:8} ^ 0x${tag:8:8}))" "$((0x${b:16:8} ^ 0x${tag:16:8}))" "$((0x${b:24:8} ^ 0x${tag:24:8}))")"

          tag="$(printf "\x${tag:0:2}\x${tag:2:2}\x${tag:4:2}\x${tag:6:2}\x${tag:8:2}\x${tag:10:2}\x${tag:12:2}\x${tag:14:2}\x${tag:16:2}\x${tag:18:2}\x${tag:20:2}\x${tag:22:2}\x${tag:24:2}\x${tag:26:2}\x${tag:28:2}\x${tag:30:2}" | $OPENSSL enc "$cipher" -K "$key" -nopad 2>/dev/null | hexdump -v -e '16/1 "%02X"')"

          b="${b:32}"
     done

     tm_out "${tag:0:tag_len}"
     return 0
}

# arg1: AES-CCM TLS cipher
# arg2: key
# arg3: nonce (must be 96 bits in length)
# arg4: ciphertext
# arg5: additional authenticated data
# arg6: expected tag (must be 16 or 32 characters)
# arg7: true if authentication tag should be checked. false otherwise.
# See Section 6.2 of NIST SP 800-38C
ccm-decrypt() {
     local cipher="$1" key="$2" nonce="$3" ciphertext="$4" aad="$5" enciphered_expected_tag="$6"
     local compute_tag="$7"
     local plaintext="" expected_tag computed_tag
     local -i i i1 i2 i3 i4 tag_len
     local -i ciphertext_len n mod_check
     local s s0

     [[ ${#nonce} -ne 24 ]] && return 7

     case "$cipher" in
          *AES_128*) cipher="-aes-128-ecb" ;;
          *AES_256*) cipher="-aes-256-ecb" ;;
          *) return 7
     esac

     ciphertext_len=${#ciphertext}
     n=$((ciphertext_len/32))
     mod_check=$((ciphertext_len%32))
     [[ $mod_check -ne 0 ]] && n+=1

     # generate keystream
     s="$(generate-ccm-counter-blocks "$nonce" "$n" | $OPENSSL enc "$cipher" -K "$key" -nopad 2>/dev/null | hexdump -v -e '16/1 "%02X"')"

     # The first 16-bytes of the keystream ($s) are used to decrypt the
     # authentication tag and the remaining bytes are used to decrypt the
     # ciphertext.
     s0="${s:0:32}"
     s="${s:32}"

     # XOR the ciphertext with the keystream ($s). For efficiency, work in blocks
     # of 16 bytes at a time (but with each XOR operation working on 32 bits.
     [[ $mod_check -ne 0 ]] && n=$((n-1))
     for (( i=0; i < n; i++ )); do
          i1=$((32*i)); i2=$((i1+8)); i3=$((i1+16)); i4=$((i1+24))
          plaintext+="$(printf "%08X%08X%08X%08X" "$((0x${ciphertext:i1:8} ^ 0x${s:i1:8}))" "$((0x${ciphertext:i2:8} ^ 0x${s:i2:8}))" "$((0x${ciphertext:i3:8} ^ 0x${s:i3:8}))" "$((0x${ciphertext:i4:8} ^ 0x${s:i4:8}))")"
     done

     # If the length of the ciphertext is not an even multiple of 16 bytes, then handle the final incomplete block.
     if [[ $mod_check -ne 0 ]]; then
          i1=$((32*n))
          for (( i=0; i < mod_check; i+=2 )); do
               plaintext+="$(printf "%02X" "$((0x${ciphertext:i1:2} ^ 0x${s:i1:2}))")"
               i1+=2
          done
     fi

     if "$compute_tag"; then
          tag_len=${#enciphered_expected_tag}

          # Decrypt the authentication tag that was provided with the message
          if [[ $tag_len -eq 16 ]]; then
               expected_tag="$(printf "%08X%08X" "$((0x${enciphered_expected_tag:0:8} ^ 0x${s0:0:8}))" "$((0x${enciphered_expected_tag:8:8} ^ 0x${s0:8:8}))")"
          elif [[ $tag_len -eq 32 ]]; then
               expected_tag="$(printf "%08X%08X%08X%08X" "$((0x${enciphered_expected_tag:0:8} ^ 0x${s0:0:8}))" "$((0x${enciphered_expected_tag:8:8} ^ 0x${s0:8:8}))" "$((0x${enciphered_expected_tag:16:8} ^ 0x${s0:16:8}))" "$((0x${enciphered_expected_tag:24:8} ^ 0x${s0:24:8}))")"
          else
               return 7
          fi

          # obtain the actual authentication tag for the decrypted message
          computed_tag="$(ccm-compute-tag "$cipher" "$key" "$nonce" "$aad" "$plaintext" "$tag_len")"
          [[ $? -ne 0 ]] && return 7
     fi

     if ! "$compute_tag" || [[ "$computed_tag" == $expected_tag ]]; then
          tm_out "$plaintext"
          return 0
     else
          return 7
     fi
}

# arg1: AES-CCM TLS cipher
# arg2: key
# arg3: nonce (must be 96 bits in length)
# arg4: plaintext
# arg5: additional authenticated data
# See Section 6.1 of NIST SP 800-38C
ccm-encrypt() {
     local cipher="$1" key="$2" nonce="$3" plaintext="$4" aad="$5"
     local -i tag_len
     local ossl_cipher="-aes-128-ecb"
     local ciphertext="" tag encrypted_tag
     local -i i i1 i2 i3 i4
     local -i plaintext_len n mod_check
     local s s0

     [[ ${#nonce} -ne 24 ]] && return 7

     case "$cipher" in
          TLS_AES_128_CCM_SHA256) tag_len=32 ;;
          TLS_AES_128_CCM_8_SHA256) tag_len=16 ;;
          *) return 7
     esac

     # compute the authentication tag
     tag="$(ccm-compute-tag "$ossl_cipher" "$key" "$nonce" "$aad" "$plaintext" "$tag_len")"
     [[ $? -ne 0 ]] && return 7

     plaintext_len=${#plaintext}
     n=$((plaintext_len/32))
     mod_check=$((plaintext_len%32))
     [[ $mod_check -ne 0 ]] && n+=1

     # generate keystream
     s="$(generate-ccm-counter-blocks "$nonce" "$n" | $OPENSSL enc "$ossl_cipher" -K "$key" -nopad 2>/dev/null | hexdump -v -e '16/1 "%02X"')"

     # encrypt the authentication tag using the first 16 bytes of the keystrem ($s)
     if [[ $tag_len -eq 16 ]]; then
          encrypted_tag="$(printf "%08X%08X" "$((0x${tag:0:8} ^ 0x${s:0:8}))" "$((0x${tag:8:8} ^ 0x${s:8:8}))")"
     elif [[ $tag_len -eq 32 ]]; then
          encrypted_tag="$(printf "%08X%08X%08X%08X" "$((0x${tag:0:8} ^ 0x${s:0:8}))" "$((0x${tag:8:8} ^ 0x${s:8:8}))" "$((0x${tag:16:8} ^ 0x${s:16:8}))" "$((0x${tag:24:8} ^ 0x${s:24:8}))")"
     else
          return 7
     fi

     # XOR the plaintext with the keystream ($s). For efficiency, work in blocks
     # of 16 bytes at a time (but with each XOR operation working on 32 bits.
     s="${s:32}"
     [[ $mod_check -ne 0 ]] && n=$((n-1))
     for (( i=0; i < n; i++ )); do
          i1=$((32*i)); i2=$((i1+8)); i3=$((i1+16)); i4=$((i1+24))
          ciphertext+="$(printf "%08X%08X%08X%08X" "$((0x${plaintext:i1:8} ^ 0x${s:i1:8}))" "$((0x${plaintext:i2:8} ^ 0x${s:i2:8}))" "$((0x${plaintext:i3:8} ^ 0x${s:i3:8}))" "$((0x${plaintext:i4:8} ^ 0x${s:i4:8}))")"
     done
     # If the length of the plaintext is not an even multiple of 16 bytes, then handle the final incomplete block.
     if [[ $mod_check -ne 0 ]]; then
          i1=$((32*n))
          for (( i=0; i < mod_check; i+=2 )); do
               ciphertext+="$(printf "%02X" "$((0x${plaintext:i1:2} ^ 0x${s:i1:2}))")"
               i1+=2
          done
     fi
     tm_out "$ciphertext$encrypted_tag"
     return 0
}

# This function is based on gcm_mult in https://github.com/mko-x/SharedAES-GCM
# args 1-16:  HL from gcm_ctx
# args 17-32: HH from gcm_ctx
# args 33-48: x - the input vector
gcm_mult() {
     local -a gcm_ctx_hl=( "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" "${10}" "${11}" "${12}" "${13}" "${14}" "${15}" "${16}" )
     local -a gcm_ctx_hh=( "${17}" "${18}" "${19}" "${20}" "${21}" "${22}" "${23}" "${24}" "${25}" "${26}" "${27}" "${28}" "${29}" "${30}" "${31}" "${32}" )
     local -a x=( "${33}" "${34}" "${35}" "${36}" "${37}" "${38}" "${39}" "${40}" "${41}" "${42}" "${43}" "${44}" "${45}" "${46}" "${47}" "${48}" )
     local output
     local -i i lo hi rem zh zl
     local -r -a -i last4=(0x0000 0x1c20 0x3840 0x2460 0x7080 0x6ca0 0x48c0 0x54e0 0xe100 0xfd20 0xd940 0xc560 0x9180 0x8da0 0xa9c0 0xb5e0)

     lo=$((0x${x[15]} & 0x0F))
     hi=$((0x${x[15]} >> 4))
     zh=0x${gcm_ctx_hh[$lo]}
     zl=0x${gcm_ctx_hl[$lo]}

     for (( i=15; i >=0; i=i-1 )); do
          lo=$((0x${x[i]} & 0x0F))
          hi=$((0x${x[i]} >> 4))
          if [[ $i -ne 15 ]]; then
               rem=$((zl & 0x0F))
               zl=$(((zl >> 4) & 0x0fffffffffffffff))
               zl=$(((zh << 60) | zl))
               zh=$(((zh >> 4) & 0x0fffffffffffffff))
               zh=$((zh^(last4[rem] << 48)))
               zh=$((zh^0x${gcm_ctx_hh[$lo]}))
               zl=$((zl^0x${gcm_ctx_hl[$lo]}))
          fi
          rem=$((zl & 0x0F))
          zl=$(((zl >> 4) & 0x0fffffffffffffff))
          zl=$(((zh << 60) | zl))
          zh=$(((zh >> 4) & 0x0fffffffffffffff))
          zh=$((zh^(last4[rem] << 48)))
          zh=$((zh^0x${gcm_ctx_hh[$hi]}))
          zl=$((zl^0x${gcm_ctx_hl[$hi]}))
     done
     output="$(printf "%016X" $zh)$(printf "%016X" $zl)"
     tm_out "${output:0:2} ${output:2:2} ${output:4:2} ${output:6:2} ${output:8:2} ${output:10:2} ${output:12:2} ${output:14:2} ${output:16:2} ${output:18:2} ${output:20:2} ${output:22:2} ${output:24:2} ${output:26:2} ${output:28:2} ${output:30:2}"
     return 0
}

# arg1: nonce (must be 96 bits)
# arg2: number of blocks needed for plaintext/ciphertext
# Generate the sequence of counter blocks, which are to be encrypted and then
# XORed with either the plaintext or the ciphertext. The first block that is
# encrypted is used in computing the authentication tag.
generate_gcm_ctr() {
     local -i nr_blocks="$1"
     local nonce="$2"
     local i
     local lsb ctr=""

     for (( i=1; i <= nr_blocks; i++ )); do
          lsb="$(printf "%08X" "$i")"
          printf "\x${nonce:0:2}\x${nonce:2:2}\x${nonce:4:2}\x${nonce:6:2}\x${nonce:8:2}\x${nonce:10:2}\x${nonce:12:2}\x${nonce:14:2}\x${nonce:16:2}\x${nonce:18:2}\x${nonce:20:2}\x${nonce:22:2}\x${lsb:0:2}\x${lsb:2:2}\x${lsb:4:2}\x${lsb:6:2}"
     done
     return 0
}

# arg1: an OpenSSL ecb cipher (e.g., -aes-128-ecb)
# arg2: key
# arg3: nonce (must be 96 bits in length)
# arg4: ciphertext
# arg5: aad
# arg6: mode
# arg7: true if authentication tag should be computed. false otherwise.
# This function is based on gcm_setkey, gcm_start, gcm_update, and gcm_finish
# in https://github.com/mko-x/SharedAES-GCM
gcm() {
     local cipher="$1" aes_key="$2" nonce="$3" input="$4" aad="$5" mode="$6"
     local compute_tag="$7"
     local -a -i gcm_ctx_hl=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
     local -a -i gcm_ctx_hh=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
     local -a -i tag
     local -a gcm_ctx_buf=("00" "00" "00" "00" "00" "00" "00" "00" "00" "00" "00" "00" "00" "00" "00" "00" )
     local -i i j hi lo vl vh t length
     local h hl="" hh="" buf ectr base_ectr tmp
     local -i input_len="$((${#input}/2))" aad_len="$((${#aad}/2))" use_len

     if "$compute_tag"; then
          # gcm_setkey - populate HL and HH from gcm_ctx
          h+=$(printf "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"| \
               $OPENSSL enc "$cipher" -K "$aes_key" -nopad 2>/dev/null | hexdump -v -e '16/1 "%02X"')
          hi=0x${h:0:8}
          lo=0x${h:8:8}
          vh=$(((hi << 32) | lo))

          hi=0x${h:16:8}
          lo=0x${h:24:8}
          vl=$(((hi << 32) | lo))

          gcm_ctx_hl[8]=$vl
          gcm_ctx_hh[8]=$vh
          gcm_ctx_hh[0]=0
          gcm_ctx_hl[0]=0

          for (( i=4; i > 0; i=i>>1 )); do
               t=$(((vl & 1) * 0xe1000000))
               vl=$(((vl >> 1) & 0x7fffffffffffffff))
               vl=$(((vh << 63) | vl))
               vh=$(((vh >> 1) & 0x7fffffffffffffff))
               vh=$((vh ^ (t << 32)))
               gcm_ctx_hl[i]=$vl
               gcm_ctx_hh[i]=$vh
          done

          for (( i=2; i < 16; i=i<<1 )); do
               vh=${gcm_ctx_hh[i]}
               vl=${gcm_ctx_hl[i]}
               for (( j=1; j < i; j++ )); do
                    gcm_ctx_hh[$((i+j))]=$((vh ^ gcm_ctx_hh[j]))
                    gcm_ctx_hl[$((i+j))]=$((vl ^ gcm_ctx_hl[j]))
               done
          done

          # place HL and HH in strings so that can be passed to gcm_mult
          for (( i=0; i < 16; i++ )); do
               hl+="$(printf "%016X" ${gcm_ctx_hl[i]}) "
               hh+="$(printf "%016X" ${gcm_ctx_hh[i]}) "
          done

          # Feed any additional authenticated data into the computation for the authentication tag.
          for (( i=0; i < aad_len; i+=use_len )); do
               [[ $((aad_len-i)) -lt 16 ]] && use_len=$((aad_len-i)) || use_len=16
               for (( j=0; j < use_len; j++ )); do
                    gcm_ctx_buf[j]="$(printf "%02X" $((0x${gcm_ctx_buf[j]} ^ 0x${aad:$((2*i+2*j)):2})))"
               done

               buf="$(gcm_mult $hl $hh ${gcm_ctx_buf[0]} ${gcm_ctx_buf[1]} ${gcm_ctx_buf[2]} ${gcm_ctx_buf[3]} ${gcm_ctx_buf[4]} ${gcm_ctx_buf[5]} ${gcm_ctx_buf[6]} ${gcm_ctx_buf[7]} ${gcm_ctx_buf[8]} ${gcm_ctx_buf[9]} ${gcm_ctx_buf[10]} ${gcm_ctx_buf[11]} ${gcm_ctx_buf[12]} ${gcm_ctx_buf[13]} ${gcm_ctx_buf[14]} ${gcm_ctx_buf[15]})"
               read -r gcm_ctx_buf[0] gcm_ctx_buf[1] gcm_ctx_buf[2] gcm_ctx_buf[3] gcm_ctx_buf[4] gcm_ctx_buf[5] gcm_ctx_buf[6] gcm_ctx_buf[7] gcm_ctx_buf[8] gcm_ctx_buf[9] gcm_ctx_buf[10] gcm_ctx_buf[11] gcm_ctx_buf[12] gcm_ctx_buf[13] gcm_ctx_buf[14] gcm_ctx_buf[15] <<< "$buf"
          done
     fi

     j=$((1 + input_len/16))
     [[ $((input_len%16)) -ne 0 ]] && j+=1
     ectr="$(generate_gcm_ctr "$j" "$nonce" | $OPENSSL enc "$cipher" -K "$aes_key" -nopad 2>/dev/null | hexdump -v -e '16/1 "%02X"')"
     base_ectr="${ectr:0:32}"
     ectr="${ectr:32}"

     # gcm_update
     # Encrypt or decrypt the input and feed the ciphertext into the computation for the authentication tag.
     for (( length=input_len; length > 0; length=length-use_len )); do
          [[ $length -lt 16 ]] && use_len=$length || use_len=16

          if [[ $use_len -eq 16 ]]; then
               tmp="$(printf "%08X%08X%08X%08X" "$((0x${ectr:0:8} ^ 0x${input:0:8}))" "$((0x${ectr:8:8} ^ 0x${input:8:8}))" "$((0x${ectr:16:8} ^ 0x${input:16:8}))" "$((0x${ectr:24:8} ^ 0x${input:24:8}))")"
          else
               tmp=""
               for (( i=0; i < use_len; i++ )); do
                    tmp+="$(printf "%02X" $((0x${ectr:$((2*i)):2} ^ 0x${input:$((2*i)):2})))"
               done
          fi
          output+="$tmp"
          if "$compute_tag"; then
               [[ $mode == decrypt ]] && tmp="${input:0:32}"
               for (( i=0; i < use_len; i++ )); do
                    gcm_ctx_buf[i]="$(printf "%02X" $((0x${gcm_ctx_buf[i]} ^ 0x${tmp:$((2*i)):2})))"
               done
          fi
          ectr="${ectr:32}"

          if "$compute_tag"; then
               tmp="$(gcm_mult $hl $hh ${gcm_ctx_buf[0]} ${gcm_ctx_buf[1]} ${gcm_ctx_buf[2]} ${gcm_ctx_buf[3]} ${gcm_ctx_buf[4]} ${gcm_ctx_buf[5]} ${gcm_ctx_buf[6]} ${gcm_ctx_buf[7]} ${gcm_ctx_buf[8]} ${gcm_ctx_buf[9]} ${gcm_ctx_buf[10]} ${gcm_ctx_buf[11]} ${gcm_ctx_buf[12]} ${gcm_ctx_buf[13]} ${gcm_ctx_buf[14]} ${gcm_ctx_buf[15]})"
               read -r gcm_ctx_buf[0] gcm_ctx_buf[1] gcm_ctx_buf[2] gcm_ctx_buf[3] gcm_ctx_buf[4] gcm_ctx_buf[5] gcm_ctx_buf[6] gcm_ctx_buf[7] gcm_ctx_buf[8] gcm_ctx_buf[9] gcm_ctx_buf[10] gcm_ctx_buf[11] gcm_ctx_buf[12] gcm_ctx_buf[13] gcm_ctx_buf[14] gcm_ctx_buf[15] <<< "$tmp"
          fi

          input="${input:$((2*use_len))}"
     done

     if "$compute_tag"; then
          # gcm_finish - feed the lengths of the ciphertext and additional authenticated data
          # into the computation for the authentication tag.
          input_len=$((8*input_len))
          aad_len=$((8*aad_len))
          output+=" "
          for (( i=0; i < 16; i++ )); do
               tag[i]=0x${base_ectr:$((2*i)):2}
          done

          if ( [[ $input_len -ne 0 ]] || [[ $aad_len -ne 0 ]] ); then
               buf="$(printf "%016X" $aad_len)$(printf "%016X" $input_len)"
               for (( i=0; i < 16; i++ )); do
                    gcm_ctx_buf[i]="$(printf "%02X" $((0x${gcm_ctx_buf[i]} ^ 0x${buf:$((2*i)):2})))"
               done

               buf="$(gcm_mult $hl $hh ${gcm_ctx_buf[0]} ${gcm_ctx_buf[1]} ${gcm_ctx_buf[2]} ${gcm_ctx_buf[3]} ${gcm_ctx_buf[4]} ${gcm_ctx_buf[5]} ${gcm_ctx_buf[6]} ${gcm_ctx_buf[7]} ${gcm_ctx_buf[8]} ${gcm_ctx_buf[9]} ${gcm_ctx_buf[10]} ${gcm_ctx_buf[11]} ${gcm_ctx_buf[12]} ${gcm_ctx_buf[13]} ${gcm_ctx_buf[14]} ${gcm_ctx_buf[15]})"
               read -r gcm_ctx_buf[0] gcm_ctx_buf[1] gcm_ctx_buf[2] gcm_ctx_buf[3] gcm_ctx_buf[4] gcm_ctx_buf[5] gcm_ctx_buf[6] gcm_ctx_buf[7] gcm_ctx_buf[8] gcm_ctx_buf[9] gcm_ctx_buf[10] gcm_ctx_buf[11] gcm_ctx_buf[12] gcm_ctx_buf[13] gcm_ctx_buf[14] gcm_ctx_buf[15] <<< "$buf"
               for (( i=0; i < 16; i++ )); do
                    tag[i]=$((tag[i] ^ 0x${gcm_ctx_buf[i]}))
               done
          fi
          for (( i=0; i < 16; i++ )); do
               output+="$(printf "%02X" ${tag[i]})"
          done
     fi
     tm_out "$output"
     return 0
}

# arg1: AES-GCM TLS cipher
# arg2: key
# arg3: nonce (must be 96 bits in length)
# arg4: ciphertext
# arg5: aad
# arg6: expected tag
# arg7: true if authentication tag should be checked. false otherwise.
gcm-decrypt() {
     local cipher="$1" key="$2" nonce="$3" ciphertext="$4" aad="$5" expected_tag="$(toupper "$6")"
     local compute_tag="$7"
     local plaintext computed_tag tmp

     [[ ${#nonce} -ne 24 ]] && return 7

     if [[ "$cipher" == TLS_AES_128_GCM_SHA256 ]] && "$HAS_AES128_GCM" && ! "$compute_tag"; then
          plaintext="$(asciihex_to_binary "$ciphertext" | \
                       $OPENSSL enc -aes-128-gcm -K "$key" -iv "$nonce" 2>/dev/null | hexdump -v -e '16/1 "%02X"')"
          tm_out "$(strip_spaces "$plaintext")"
          return 0
     elif [[ "$cipher" == TLS_AES_256_GCM_SHA384 ]] && "$HAS_AES256_GCM" && ! "$compute_tag"; then
          plaintext="$(asciihex_to_binary "$ciphertext" | \
                       $OPENSSL enc -aes-256-gcm -K "$key" -iv "$nonce" 2>/dev/null | hexdump -v -e '16/1 "%02X"')"
          tm_out "$(strip_spaces "$plaintext")"
          return 0
     fi

     case "$cipher" in
          *AES_128*) cipher="-aes-128-ecb" ;;
          *AES_256*) cipher="-aes-256-ecb" ;;
          *) return 7
     esac

     tmp="$(gcm "$cipher" "$key" "$nonce" "$ciphertext" "$aad" "decrypt" "$compute_tag")"
     [[ $? -ne 0 ]] && return 7
     computed_tag="${tmp##* }"
     plaintext="${tmp% $computed_tag}"

     if ! "$compute_tag" || [[ "$computed_tag" == $expected_tag ]]; then
          tm_out "$plaintext"
          return 0
     else
          return 7
     fi
}

# arg1: AES-GCM TLS cipher
# arg2: key
# arg3: nonce (must be 96 bits in length)
# arg4: plaintext
# arg5: aad
# See Section 7.2 of SP 800-38D
gcm-encrypt() {
     local cipher

     case "$1" in
          *AES_128*) cipher="-aes-128-ecb" ;;
          *AES_256*) cipher="-aes-256-ecb" ;;
          *) return 7
     esac
     [[ ${#3} -ne 24 ]] && return 7

     tm_out "$(gcm "$cipher" "$2" "$3" "$4" "$5" "encrypt" true)"
     return $?
}

# arg1: TLS cipher
# arg2: key
# arg3: nonce (must be 96 bits in length)
# arg4: ciphertext
# arg5: additional authenticated data
sym-decrypt() {
     local cipher="$1"
     local key="$2" nonce="$3"
     local ciphertext="$4"
     local additional_data="$5"
     local plaintext
     local -i ciphertext_len tag_len
     local compute_tag=false

     case "$cipher" in
          *CCM_8*)
               tag_len=16 ;;
          *CCM*|*GCM*|*CHACHA20_POLY1305*)
               tag_len=32 ;;
          *)
               return 7 ;;
     esac

     # The final $tag_len characters of the ciphertext are the authentication tag
     ciphertext_len=${#ciphertext}
     [[ $ciphertext_len -lt $tag_len ]] && return 7
     ciphertext_len=$((ciphertext_len-tag_len))

     # In general there is no need to verify that the authentication tag is correct
     # when decrypting, and performing the check is time consuming when the
     # computations are performed in Bash. If the ciphertext is very long (e.g.,
     # some application data), then trying to compute the authentication tag is
     # too time consuming even for debug mode.
     [[ $DEBUG -ge 1 ]] && [[ $ciphertext_len -le 1024 ]] && compute_tag=true

     if [[ "$cipher" =~ CHACHA20_POLY1305 ]]; then
          plaintext="$(chacha20_aead_decrypt "$key" "$nonce" "${ciphertext:0:ciphertext_len}" "$additional_data" "${ciphertext:ciphertext_len:tag_len}" "$compute_tag")"
     elif [[ "$cipher" =~ CCM ]]; then
          plaintext=$(ccm-decrypt "$cipher" "$key" "$nonce" "${ciphertext:0:ciphertext_len}" "$additional_data" "${ciphertext:ciphertext_len:tag_len}" "$compute_tag")
     else # GCM
          plaintext=$(gcm-decrypt "$cipher" "$key" "$nonce" "${ciphertext:0:ciphertext_len}" "$additional_data" "${ciphertext:ciphertext_len:tag_len}" "$compute_tag")
     fi
     [[ $? -ne 0 ]] && return 7

     tm_out "$plaintext"
     return 0
}

# arg1: TLS cipher
# arg2: key
# arg3: nonce (must be 96 bits in length)
# arg4: plaintext
# arg5: additional authenticated data
sym-encrypt() {
     local cipher="$1" key="$2" nonce="$3" plaintext="$4" additional_data="$5"
     local ciphertext=""


     if [[ "$cipher" =~ CCM ]]; then
          ciphertext=$(ccm-encrypt "$cipher" "$key" "$nonce" "$plaintext" "$additional_data")
     elif [[ "$cipher" =~ GCM ]]; then
          ciphertext=$(gcm-encrypt "$cipher" "$key" "$nonce" "$plaintext" "$additional_data")
     elif [[ "$cipher" =~ CHACHA20_POLY1305 ]]; then
          ciphertext="$(chacha20_aead_encrypt "$key" "$nonce" "$plaintext" "$additional_data")"
     else
          return 7
     fi
     [[ $? -ne 0 ]] && return 7

     tm_out "$(strip_spaces "$ciphertext")"
     return 0
}

# arg1: iv
# arg2: sequence number
get-nonce() {
     local iv="$1"
     local -i seq_num="$2"
     local -i len lsb
     local msb nonce

     len=${#iv}
     [[ $len -lt 8 ]] && return 7
     i=$len-8
     msb="${iv:0:i}"
     lsb="0x${iv:i:8}"
     nonce="${msb}$(printf "%08X" "$((lsb ^ seq_num))")"
     tm_out "$nonce"
     return 0
}

# Return:
#     0 if arg1 contains the entire server response.
#     1 if arg1 does not contain the entire server response.
#     2 if the response is malformed.
#     3 if (a) the response version is TLSv1.3;
#          (b) arg1 contains the entire ServerHello (and appears to contain the entire response);
#          (c) the entire response is supposed to be parsed; and
#          (d) the key and IV have not been provided to decrypt the response.
# arg1: ASCII-HEX encoded reply
# arg2: whether to process the full request ("all") or just the basic request plus the ephemeral key if any ("ephemeralkey").
# arg3: TLS cipher for decrypting TLSv1.3 response
# arg4: handshake secret
# arg5: message transcript (up through ServerHello)
check_tls_serverhellodone() {
     local tls_hello_ascii="$1"
     local process_full="$2"
     local cipher="$3"
     local handshake_secret="$4"
     local msg_transcript="$5"
     local tls_handshake_ascii="" tls_alert_ascii=""
     local -i i tls_hello_ascii_len tls_handshake_ascii_len tls_alert_ascii_len
     local -i msg_len remaining tls_serverhello_ascii_len sid_len
     local -i j offset tls_extensions_len extension_len
     local tls_content_type tls_protocol tls_msg_type extension_type
     local tls_err_level
     local hash_fn handshake_traffic_keys key="" iv="" finished_key=""
     local -i seq_num=0 plaintext_len
     local plaintext decrypted_response="" additional_data
     local include_headers=true

     DETECTED_TLS_VERSION=""

     if [[ -n "$handshake_secret" ]]; then
          handshake_traffic_keys="$(derive-handshake-traffic-keys "$cipher" "$handshake_secret" "$msg_transcript" "server")"
          read -r key iv finished_key <<< "$handshake_traffic_keys"
     fi

     if [[ -z "$tls_hello_ascii" ]]; then
          return 0              # no server hello received
     fi

     tls_hello_ascii_len=${#tls_hello_ascii}
     for (( i=0; i<tls_hello_ascii_len; i+=msg_len )); do
          remaining=$tls_hello_ascii_len-$i
          [[ $remaining -lt 10 ]] && return 1

          tls_content_type="${tls_hello_ascii:i:2}"
          case "$tls_content_type" in
               14|15|16|17) ;;
               *) return 2 ;;
          esac
          i+=2
          tls_protocol="${tls_hello_ascii:i:4}"
          [[ -z "$DETECTED_TLS_VERSION" ]] && DETECTED_TLS_VERSION="$tls_protocol"
          [[ "${tls_protocol:0:2}" != 03 ]] && return 2
          i+=4
          additional_data="$tls_content_type$tls_protocol${tls_hello_ascii:i:4}"
          msg_len=2*$(hex2dec "${tls_hello_ascii:i:4}")
          i+=4
          remaining=$tls_hello_ascii_len-$i
          [[ $msg_len -gt $remaining ]] && return 1

          if [[ "$tls_content_type" == 16 ]]; then
               tls_handshake_ascii+="${tls_hello_ascii:i:msg_len}"
               tls_handshake_ascii_len=${#tls_handshake_ascii}
               decrypted_response+="$tls_content_type$tls_protocol$(printf "%04X" $((msg_len/2)))${tls_hello_ascii:i:msg_len}"
               # the ServerHello MUST be the first handshake message
               [[ $tls_handshake_ascii_len -ge 2 ]] && [[ "${tls_handshake_ascii:0:2}" != 02 ]] && return 2
               if [[ $tls_handshake_ascii_len -ge 12 ]]; then
                    DETECTED_TLS_VERSION="${tls_handshake_ascii:8:4}"

                    # In TLSv1.3 (starting with draft 22), the version field specifies TLSv1.2, but
                    # there is a supported_versions extension that specifies the actual version. So,
                    # if the version field specifies TLSv1.2, then check to see if there is a
                    # supported_versions extension.
                    if [[ "$DETECTED_TLS_VERSION" == 0303 ]]; then
                         tls_serverhello_ascii_len=2*$(hex2dec "${tls_handshake_ascii:2:6}")
                         sid_len=2*$(hex2dec "${tls_handshake_ascii:76:2}")
                         if [[ $tls_serverhello_ascii_len -gt 76+$sid_len ]]; then
                              # ServerHello contains extensions, so check for supported_versions extension
                              offset=84+$sid_len
                              tls_extensions_len=2*$(hex2dec "${tls_handshake_ascii:offset:4}")
                              [[ $tls_extensions_len -ne $tls_serverhello_ascii_len-$sid_len-80 ]] && return 2
                              for (( j=0; j<tls_extensions_len; j+=8+extension_len )); do
                                   [[ $tls_extensions_len-$j -lt 8 ]] && return 2
                                   offset=88+$sid_len+$j
                                   extension_type="${tls_handshake_ascii:offset:4}"
                                   offset=92+$sid_len+$j
                                   extension_len=2*$(hex2dec "${tls_handshake_ascii:offset:4}")
                                   [[ $extension_len -gt $tls_extensions_len-$j-8 ]] && return 2
                                   if [[ "$extension_type" == 002B ]]; then # supported_versions
                                        [[ $extension_len -ne 4 ]] && return 2
                                        offset=96+$sid_len+$j
                                        DETECTED_TLS_VERSION="${tls_handshake_ascii:offset:4}"
                                   fi
                              done
                         fi
                    fi
                    # A version of {0x7F, xx} represents an implementation of a draft version of TLS 1.3
                    if [[ "${DETECTED_TLS_VERSION:0:2}" == 7F ]]; then
                         [[ 0x${DETECTED_TLS_VERSION:2:2} -lt 25 ]] && include_headers=false
                         DETECTED_TLS_VERSION=0304
                    fi
                    if [[ 0x$DETECTED_TLS_VERSION -ge 0x0304 ]] && [[ "$process_full" == ephemeralkey ]]; then
                         tls_serverhello_ascii_len=2*$(hex2dec "${tls_handshake_ascii:2:6}")
                         if [[ $tls_handshake_ascii_len -ge $tls_serverhello_ascii_len+8 ]]; then
                              tm_out ""
                              return 0 # The entire ServerHello message has been received (and the rest isn't needed)
                         fi
                    fi
               fi
          elif [[ "$tls_content_type" == 15 ]]; then   # TLS ALERT
               tls_alert_ascii+="${tls_hello_ascii:i:msg_len}"
               decrypted_response+="$tls_content_type$tls_protocol$(printf "%04X" $((msg_len/2)))${tls_hello_ascii:i:msg_len}"
          elif [[ "$tls_content_type" == 17 ]] && [[ -n "$key" ]]; then # encrypted data
               # The header information was added to additional data in TLSv1.3 draft 25.
               "$include_headers" || additional_data=""
               nonce="$(get-nonce "$iv" "$seq_num")"
               [[ $? -ne 0 ]] && return 2
               plaintext="$(sym-decrypt "$cipher" "$key" "$nonce" "${tls_hello_ascii:i:msg_len}" "$additional_data")"
               [[ $? -ne 0 ]] && return 2
               seq_num+=1

               # Remove zeros from end of plaintext, if any
               plaintext_len=${#plaintext}-2
               while [[ "${plaintext:plaintext_len:2}" == 00 ]]; do
                    plaintext_len=$plaintext_len-2
               done
               tls_content_type="${plaintext:plaintext_len:2}"
               decrypted_response+="${tls_content_type}0301$(printf "%04X" $((plaintext_len/2)))${plaintext:0:plaintext_len}"
               case "$tls_content_type" in
                    15) tls_alert_ascii+="${plaintext:0:plaintext_len}" ;;
                    16) tls_handshake_ascii+="${plaintext:0:plaintext_len}" ;;
                    *) return 2 ;;
               esac
          fi
     done

     # If there is a fatal alert, then we are done.
     tls_alert_ascii_len=${#tls_alert_ascii}
     for (( i=0; i<tls_alert_ascii_len; i+=4 )); do
          remaining=$tls_alert_ascii_len-$i
          [[ $remaining -lt 4 ]] && return 1
          tls_err_level=${tls_alert_ascii:i:2}    # 1: warning, 2: fatal
          [[ $tls_err_level == 02 ]] && DETECTED_TLS_VERSION="" && tm_out "" && return 0
     done

     # If there is a serverHelloDone or Finished, then we are done.
     tls_handshake_ascii_len=${#tls_handshake_ascii}
     for (( i=0; i<tls_handshake_ascii_len; i+=msg_len )); do
          remaining=$tls_handshake_ascii_len-$i
          [[ $remaining -lt 8 ]] && return 1
          tls_msg_type="${tls_handshake_ascii:i:2}"
          i+=2
          msg_len=2*$(hex2dec "${tls_handshake_ascii:i:6}")
          i+=6
          remaining=$tls_handshake_ascii_len-$i
          [[ $msg_len -gt $remaining ]] && return 1

          # The ServerHello has already been added to $msg_transcript,
          # but all other handshake messages need to be added.
          if [[ -n "$key" ]] && [[ "$tls_msg_type" != 02 ]]; then
               if [[ $DEBUG -ge 1 ]] && [[ "$tls_msg_type" == 14 ]]; then
                    # Check the Finished message
                    if [[ "$cipher" == *SHA256 ]]; then
                         hash_fn="-sha256"
                         [[ $msg_len -eq 64 ]] || return 2
                    elif [[ "$cipher" == *SHA384 ]]; then
                         hash_fn="-sha384"
                         [[ $msg_len -eq 96 ]] || return 2
                    else
                         return 2
                    fi
                    [[ "${tls_handshake_ascii:i:msg_len}" != $(hmac-transcript "$hash_fn" "$finished_key" "$msg_transcript") ]] && \
                         return 2
               fi
               msg_transcript+="$tls_msg_type${tls_handshake_ascii:$((i-6)):6}${tls_handshake_ascii:i:msg_len}"
          fi
          # For SSLv3 - TLS1.2 look for a ServerHelloDone message.
          # For TLS 1.3 look for a Finished message.
          [[ $tls_msg_type == 0E ]] && tm_out "" && return 0
          [[ $tls_msg_type == 14 ]] && tm_out "$msg_transcript $decrypted_response" && return 0
     done
     # If the response is TLSv1.3 and the full response is to be processed, but the
     # key and IV have not been provided to decrypt the response, then return 3 if
     # the entire ServerHello has been received.
     if [[ "$DETECTED_TLS_VERSION" == 0304 ]] && [[ "$process_full" =~ all ]] && \
        [[ -z "$handshake_secret" ]] && [[ $tls_handshake_ascii_len -gt 0 ]]; then
          return 3
     fi
     # If we haven't encountered a fatal alert or a server hello done,
     # then there must be more data to retrieve.
     return 1
}

# arg1: tls alert error/warning code
# returns: description
tls_alert() {
     local tls_alert_text=""

     case "$1" in
          00) tls_alert_text="close notify" ;;
          0A) tls_alert_text="unexpected message" ;;
          14) tls_alert_text="bad record mac" ;;
          15) tls_alert_text="decryption failed" ;;
          16) tls_alert_text="record overflow" ;;
          1E) tls_alert_text="decompression failure" ;;
          28) tls_alert_text="handshake failure" ;;
          29) tls_alert_text="no certificate RESERVED" ;;
          2A) tls_alert_text="bad certificate" ;;
          2B) tls_alert_text="unsupported certificate" ;;
          2C) tls_alert_text="certificate revoked" ;;
          2D) tls_alert_text="certificate expired" ;;
          2E) tls_alert_text="certificate unknown" ;;
          2F) tls_alert_text="illegal parameter" ;;
          30) tls_alert_text="unknown ca" ;;
          31) tls_alert_text="access denied" ;;
          32) tls_alert_text="decode error" ;;
          33) tls_alert_text="decrypt error" ;;
          3C) tls_alert_text="export restriction RESERVED" ;;
          46) tls_alert_text="protocol version" ;;
          47) tls_alert_text="insufficient security" ;;
          50) tls_alert_text="internal error" ;;
          56) tls_alert_text="inappropriate fallback" ;;
          5A) tls_alert_text="user canceled" ;;
          64) tls_alert_text="no renegotiation" ;;
          6D) tls_alert_text="missing extension" ;;
          6E) tls_alert_text="unsupported extension" ;;
          6F) tls_alert_text="certificate unobtainable" ;;
          70) tls_alert_text="unrecognized name" ;;
          71) tls_alert_text="bad certificate status response" ;;
          72) tls_alert_text="bad certificate hash value" ;;
          73) tls_alert_text="unknown psk identity" ;;
          74) tls_alert_text="certificate required" ;;
          78) tls_alert_text="no application protocol" ;;
           *) tls_alert_text="$(hex2dec "$1")";;
     esac
     echo "$tls_alert_text"
     return 0
}

# arg1: ASCII-HEX encoded reply
# arg2: (optional): "all" or "all+" - process full response (including Certificate and certificate_status handshake messages)
#                   "ephemeralkey"  - extract the server's ephemeral key (if any)
# arg3: (optional): CIPHER_SUITES string (lowercase, and in the format output by code2network())
#       If present, parse_tls_serverhello() will check that the cipher in the ServerHello appears in
#       the CIPHER_SUITES string.
parse_tls_serverhello() {
     local tls_hello_ascii="$1"
     local process_full="$2"
     local cipherlist="$3"
     local tls_handshake_ascii="" tls_alert_ascii=""
     local -i tls_hello_ascii_len tls_handshake_ascii_len tls_alert_ascii_len msg_len
     local tls_serverhello_ascii="" tls_certificate_ascii=""
     local tls_serverkeyexchange_ascii="" tls_certificate_status_ascii=""
     local tls_encryptedextensions_ascii="" tls_revised_certificate_msg=""
     local -i tls_serverhello_ascii_len=0 tls_certificate_ascii_len=0
     local -i tls_serverkeyexchange_ascii_len=0 tls_certificate_status_ascii_len=0
     local -i tls_encryptedextensions_ascii_len=0
     local added_encrypted_extensions=false
     local tls_alert_descrip tls_sid_len_hex issuerDN subjectDN CAissuerDN CAsubjectDN
     local -i tls_sid_len offset extns_offset nr_certs=0
     local tls_msg_type tls_content_type tls_protocol tls_protocol2 tls_hello_time
     local tls_err_level tls_err_descr_no tls_cipher_suite rfc_cipher_suite tls_compression_method
     local tls_extensions="" extension_type named_curve_str="" named_curve_oid
     local -i i j extension_len extn_len tls_extensions_len ocsp_response_len=0 ocsp_response_list_len ocsp_resp_offset
     local -i certificate_list_len certificate_len cipherlist_len
     local -i curve_type named_curve
     local -i dh_bits=0 msb mask
     local hostcert_issuer=""
     local len1 len2 len3 key_bitstring="" pem_certificate
     local dh_p dh_param ephemeral_param rfc7919_param
     local -i dh_p_len dh_param_len

     DETECTED_TLS_VERSION=""
     [[ $DEBUG -ge 1 ]] && echo > $TMPFILE

     [[ "$DEBUG" -ge 5 ]] && echo $tls_hello_ascii      # one line without any blanks

     # Client messages, including handshake messages, are carried by the record layer.
     # First, extract the handshake and alert messages.
     # see https://en.wikipedia.org/wiki/Transport_Layer_Security#TLS_record
     # byte 0:      content type:                 0x14=CCS,    0x15=TLS alert  x16=Handshake,  0x17 Application, 0x18=HB
     # byte 1+2:    TLS version word, major is 03, minor 00=SSL3, 01=TLS1 02=TLS1.1 03=TLS 1.2
     # byte 3+4:    fragment length
     # bytes 5...:  message fragment
     tls_hello_ascii_len=${#tls_hello_ascii}
     if [[ $DEBUG -ge 3 ]] && [[ $tls_hello_ascii_len -gt 0 ]]; then
          echo "TLS message fragments:"
     fi
     for (( i=0; i<tls_hello_ascii_len; i+=msg_len )); do
          if [[ $tls_hello_ascii_len-$i -lt 10 ]]; then
               if [[ "$process_full" =~ all ]]; then
                    # The entire server response should have been retrieved.
                    debugme tmln_warning "Malformed message."
                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                    return 1
               else
                    # This could just be a result of the server's response being
                    # split across two or more packets.
                    break
               fi
          fi
          tls_content_type="${tls_hello_ascii:i:2}"
          i+=2
          tls_protocol="${tls_hello_ascii:i:4}"
          i+=4
          msg_len=2*$(hex2dec "${tls_hello_ascii:i:4}")
          i+=4

          if [[ $DEBUG -ge 3 ]]; then
               echo  "     protocol (rec. layer):  0x$tls_protocol"
               echo -n "     tls_content_type:       0x$tls_content_type"
               case $tls_content_type in
                    14) tmln_out " (change cipher spec)" ;;
                    15) tmln_out " (alert)" ;;
                    16) tmln_out " (handshake)" ;;
                    17) tmln_out " (application data)" ;;
                     *) tmln_out ;;
               esac
               echo "     msg_len:                $((msg_len/2))"
               tmln_out
          fi

          if "$do_starttls" ; then
               if [[ $tls_content_type == 35 ]] || [[ $tls_content_type == 34 ]]; then
                    # STARTTLS handshake failed and server replied plaintext with a 5xx or 4xx
                    [[ $DEBUG -ge 2 ]] && printf "%s\n" "400/500: $(hex2ascii "$tls_hello_ascii" 2>/dev/null)"
                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                    return 4
               elif [[ "$tls_hello_ascii" =~ 6130303220 ]]; then
                    [[ $DEBUG -ge 2 ]] && printf "%s\n" "probably IMAP plaintext reply \"$(hex2ascii "${tls_hello_ascii:0:32}" 2>/dev/null)\""
                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                    return 3
               fi
          fi
          if [[ $tls_content_type != 14 ]] && [[ $tls_content_type != 15 ]] && \
               [[ $tls_content_type != 16 ]] && [[ $tls_content_type != 17 ]]; then
               debugme tmln_warning "Content type other than alert, handshake, change cipher spec, or application data detected."
               [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
               return 1
          elif [[ "${tls_protocol:0:2}" != 03 ]]; then
               debugme tmln_warning "Protocol record_version.major is not 03."
               [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
               return 1
          fi
          DETECTED_TLS_VERSION=$tls_protocol

          if [[ $msg_len -gt $tls_hello_ascii_len-$i ]]; then
               if [[ "$process_full" =~ all ]]; then
                    debugme tmln_warning "Malformed message."
                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                    return 7
               else
                    # This could just be a result of the server's response being split
                    # across two or more packets. Just grab the part that is available.
                    msg_len=$tls_hello_ascii_len-$i
               fi
          fi

          case "$tls_content_type" in
               15) tls_alert_ascii="$tls_alert_ascii${tls_hello_ascii:i:msg_len}" ;;
               16) tls_handshake_ascii="$tls_handshake_ascii${tls_hello_ascii:i:msg_len}" ;;
          esac
     done

     # Now check the alert messages.
     tls_alert_ascii_len=${#tls_alert_ascii}
     if [[ "$process_full" =~ all ]] && [[ $tls_alert_ascii_len%4 -ne 0 ]]; then
          debugme tmln_warning "Malformed message."
          [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
          return 1
     fi

     if [[ $tls_alert_ascii_len -gt 0 ]]; then
          debugme echo "TLS alert messages:"
          for (( i=0; i+3 < tls_alert_ascii_len; i+=4 )); do
               tls_err_level=${tls_alert_ascii:i:2}    # 1: warning, 2: fatal
               j=$i+2
               tls_err_descr_no=${tls_alert_ascii:j:2}
               if [[ $DEBUG -ge 1 ]]; then
                    debugme tm_out  "     tls_err_descr_no:       0x${tls_err_descr_no} / = $(hex2dec ${tls_err_descr_no})"
                    tls_alert_descrip="$(tls_alert "$tls_err_descr_no")"
                    if [[ $DEBUG -ge 2 ]]; then
                         tmln_out " ($tls_alert_descrip)"
                         tm_out  "     tls_err_level:          ${tls_err_level}"
                    fi
                    case $tls_err_level in
                         01) echo -n "warning " >> $TMPFILE
                             debugme tmln_out " (warning)" ;;
                         02) echo -n "fatal " >> $TMPFILE
                             debugme tmln_out " (fatal)" ;;
                    esac
                    echo "alert $tls_alert_descrip" >> $TMPFILE
                    echo "===============================================================================" >> $TMPFILE
               fi

               if [[ "$tls_err_level" != 01 ]] && [[ "$tls_err_level" != 02 ]]; then
                    debugme tmln_warning "Unexpected AlertLevel (0x$tls_err_level)."
                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                    return 1
               elif [[ "$tls_err_level" == 02 ]]; then
                    # Fatal alert
                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                    return 1
               fi
          done
     fi

     # Now extract just the server hello, certificate, certificate status,
     # and server key exchange handshake messages.
     tls_handshake_ascii_len=${#tls_handshake_ascii}
     if [[ $DEBUG -ge 3 ]] && [[ $tls_handshake_ascii_len -gt 0 ]]; then
          echo "TLS handshake messages:"
     fi
     for (( i=0; i<tls_handshake_ascii_len; i+=msg_len )); do
          if [[ $tls_handshake_ascii_len-$i -lt 8 ]]; then
               if [[ "$process_full" =~ all ]]; then
                    # The entire server response should have been retrieved.
                    debugme tmln_warning "Malformed message."
                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                    return 1
               else
                    # This could just be a result of the server's response being
                    # split across two or more packets.
                    continue
               fi
          fi
          tls_msg_type="${tls_handshake_ascii:i:2}"
          i+=2
          msg_len=2*$(hex2dec "${tls_handshake_ascii:i:6}")
          i+=6
          if [[ $DEBUG -ge 3 ]]; then
               tm_out  "     handshake type:         0x${tls_msg_type}"
               case $tls_msg_type in
                    00) tmln_out " (hello_request)" ;;
                    01) tmln_out " (client_hello)" ;;
                    02) tmln_out " (server_hello)" ;;
                    03) tmln_out " (hello_verify_request)" ;;
                    04) tmln_out " (new_session_ticket)" ;;
                    05) tmln_out " (end_of_early_data)" ;;
                    06) tmln_out " (hello_retry_request)" ;;
                    08) tmln_out " (encrypted_extensions)" ;;
                    0B) tmln_out " (certificate)" ;;
                    0C) tmln_out " (server_key_exchange)" ;;
                    0D) tmln_out " (certificate_request)" ;;
                    0E) tmln_out " (server_hello_done)" ;;
                    0F) tmln_out " (certificate_verify)" ;;
                    10) tmln_out " (client_key_exchange)" ;;
                    14) tmln_out " (finished)" ;;
                    15) tmln_out " (certificate_url)" ;;
                    16) tmln_out " (certificate_status)" ;;
                    17) tmln_out " (supplemental_data)" ;;
                    18) tmln_out " (key_update)" ;;
                    19) tmln_out " (compressed_certificate)" ;;
                    FE) tmln_out " (message_hash)" ;;
                    *) tmln_out ;;
               esac
               echo "     msg_len:                $((msg_len/2))"
               tmln_out
          fi
          if [[ $msg_len -gt $tls_handshake_ascii_len-$i ]]; then
               if [[ "$process_full" =~ all ]]; then
                    debugme tmln_warning "Malformed message."
                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                    return 1
               else
                    # This could just be a result of the server's response being
                    # split across two or more packets. Just grab the part that
                    # is available.
                    msg_len=$tls_handshake_ascii_len-$i
               fi
          fi

          if [[ "$tls_msg_type" == 02 ]]; then
               if [[ -n "$tls_serverhello_ascii" ]]; then
                    debugme tmln_warning "Response contained more than one ServerHello handshake message."
                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                    return 1
               fi
               tls_serverhello_ascii="${tls_handshake_ascii:i:msg_len}"
               tls_serverhello_ascii_len=$msg_len
          elif [[ "$process_full" =~ all ]] && [[ "$tls_msg_type" == 08 ]]; then
               # Add excrypted extensions (now decrypted) to end of extensions in SeverHello
               tls_encryptedextensions_ascii="${tls_handshake_ascii:i:msg_len}"
               tls_encryptedextensions_ascii_len=$msg_len
               if [[ $msg_len -lt 2 ]]; then
                    debugme tmln_warning "Response contained a malformed encrypted extensions message"
                    return 1
               fi
          elif [[ "$process_full" =~ all ]] && [[ "$tls_msg_type" == 0B ]]; then
               if [[ -n "$tls_certificate_ascii" ]]; then
                    debugme tmln_warning "Response contained more than one Certificate handshake message."
                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                    return 1
               fi
               tls_certificate_ascii="${tls_handshake_ascii:i:msg_len}"
               tls_certificate_ascii_len=$msg_len
          elif ( [[ "$process_full" =~ all ]] || [[ "$process_full" == ephemeralkey ]] ) && [[ "$tls_msg_type" == 0C ]]; then
               if [[ -n "$tls_serverkeyexchange_ascii" ]]; then
                    debugme tmln_warning "Response contained more than one ServerKeyExchange handshake message."
                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                    return 1
               fi
               tls_serverkeyexchange_ascii="${tls_handshake_ascii:i:msg_len}"
               tls_serverkeyexchange_ascii_len=$msg_len
          elif [[ "$process_full" =~ all ]] && [[ "$tls_msg_type" == 16 ]]; then
               if [[ -n "$tls_certificate_status_ascii" ]]; then
                    debugme tmln_warning "Response contained more than one certificate_status handshake message."
                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                    return 1
               fi
               tls_certificate_status_ascii="${tls_handshake_ascii:i:msg_len}"
               tls_certificate_status_ascii_len=$msg_len
          elif [[ "$tls_msg_type" == 19 ]]; then
               if [[ -n "$tls_certificate_ascii" ]]; then
                    debugme tmln_warning "Response contained more than one Certificate handshake message."
                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                    return 1
               fi
               if [[ $DEBUG -ge 3 ]]; then
                    tm_out "          Certificate Compression Algorithm: ${tls_handshake_ascii:i:4}"
                    case ${tls_handshake_ascii:i:4} in
                         0001) tmln_out " (ZLIB)" ;;
                         0002) tmln_out " (Brotli)" ;;
                         0003) tmln_out " (Zstandard)" ;;
                         *)    tmln_out ;;
                    esac
                    offset=$((i+4))
                    tmln_out "          Uncompressed certificate length:   $(printf "%d" 0x${tls_handshake_ascii:offset:6})"
                    tmln_out
               fi
               if [[ "$process_full" =~ all ]] && "$HAS_ZLIB" && [[ "${tls_handshake_ascii:i:4}" == 0001 ]]; then
                    offset=$((i+4))
                    tls_certificate_ascii_len=2*0x${tls_handshake_ascii:offset:6}
                    offset=$((i+16))
                    len1=$((msg_len-16))
                    tls_certificate_ascii="$(asciihex_to_binary "${tls_handshake_ascii:offset:len1}" | $OPENSSL zlib -d 2>/dev/null | hexdump -v -e '16/1 "%02X"')"
                    tls_certificate_ascii="${tls_certificate_ascii%%[!0-9A-F]*}"
                    if [[ ${#tls_certificate_ascii} -ne $tls_certificate_ascii_len ]]; then
                         debugme tmln_warning "Length of uncompressed certificates did not match specified length."
                         return 1
                    fi
               fi
          fi
     done

     if [[ $tls_serverhello_ascii_len -eq 0 ]]; then
          debugme echo "server hello empty, TCP connection closed"
          DETECTED_TLS_VERSION="closed TCP connection "
          [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
          return 1              # no server hello received
     elif [[ $tls_serverhello_ascii_len -lt 76 ]]; then
          DETECTED_TLS_VERSION="reply malformed"
          debugme echo "Malformed response"
          [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
          return 1
     elif [[ "${tls_handshake_ascii:0:2}" != 02 ]]; then
          # the ServerHello MUST be the first handshake message
          DETECTED_TLS_VERSION="reply contained no ServerHello"
          debugme tmln_warning "The first handshake protocol message is not a ServerHello."
          [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
          return 1
     fi
     if [[ $DEBUG -eq 0 ]]; then
          echo "CONNECTED(00000003)" > $TMPFILE
     else
          echo "CONNECTED(00000003)" >> $TMPFILE
     fi

     # First parse the server hello handshake message
     # byte 0+1:    03, TLS version word          see byte 1+2
     # byte 2-5:    TLS timestamp                 for OpenSSL <1.01f
     # byte 6-33:  random, 28 bytes
     # byte 34:     session id length
     # byte 35+36+sid-len:  cipher suite!
     # byte 37+sid-len:     compression method:  00: none, 01: deflate, 64: LZS
     # byte 38+39+sid-len:  extension length
     tls_protocol2="${tls_serverhello_ascii:0:4}"
     DETECTED_TLS_VERSION="$tls_protocol2"
     [[ "${DETECTED_TLS_VERSION:0:2}" == 7F ]] && DETECTED_TLS_VERSION="0304"
     if [[ "${DETECTED_TLS_VERSION:0:2}" != 03 ]]; then
          debugme tmln_warning "server_version.major in ServerHello is not 03."
          [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
          return 1
     fi

     if [[ "0x${DETECTED_TLS_VERSION:2:2}" -le "0x03" ]]; then
          tls_hello_time="${tls_serverhello_ascii:4:8}"
          [[ "$TLS_DIFFTIME_SET" || "$DEBUG" ]] && TLS_TIME=$(hex2dec "$tls_hello_time")
          tls_sid_len_hex="${tls_serverhello_ascii:68:2}"
          tls_sid_len=2*$(hex2dec "$tls_sid_len_hex")
          offset=$((tls_sid_len+70))
          if [[ $tls_serverhello_ascii_len -lt 76+$tls_sid_len ]]; then
               debugme echo "Malformed response"
               [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
               return 1
          fi
     else
          offset=68
     fi

     tls_cipher_suite="${tls_serverhello_ascii:offset:4}"

     if [[ "0x${DETECTED_TLS_VERSION:2:2}" -le "0x03" ]]; then
          offset=$((tls_sid_len+74))
          tls_compression_method="${tls_serverhello_ascii:offset:2}"
          extns_offset=$((tls_sid_len+76))
     else
          extns_offset=72
     fi

     if [[ $tls_serverhello_ascii_len -gt $extns_offset ]] && \
        ( [[ "$process_full" =~ all ]] || [[ "$DETECTED_TLS_VERSION" == 0303 ]] || \
          ( [[ "$process_full" == ephemeralkey ]] && [[ "0x${DETECTED_TLS_VERSION:2:2}" -gt "0x03" ]] ) ); then
          if [[ $tls_serverhello_ascii_len -lt $extns_offset+4 ]]; then
               debugme echo "Malformed response"
               [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
               return 1
          fi
          tls_extensions_len=$(hex2dec "${tls_serverhello_ascii:extns_offset:4}")*2
          if [[ $tls_extensions_len -ne $tls_serverhello_ascii_len-$extns_offset-4 ]]; then
               debugme tmln_warning "Malformed message."
               [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
               return 1
          fi
          for (( i=0; i<tls_extensions_len; i+=8+extension_len )); do
               if [[  $tls_extensions_len-$i -lt 8 ]]; then
                    debugme echo "Malformed response"
                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                    return 1
               fi
               offset=$((extns_offset+i+4))
               extension_type="${tls_serverhello_ascii:offset:4}"
               offset=$((extns_offset+i+8))
               extension_len=2*$(hex2dec "${tls_serverhello_ascii:offset:4}")
               if [[  $extension_len -gt $tls_extensions_len-$i-8 ]]; then
                    debugme echo "Malformed response"
                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                    return 1
               fi
               case $extension_type in
                    0000) tls_extensions+="TLS server extension \"server name\" (id=0), len=$extension_len\n" ;;
                    0001) tls_extensions+="TLS server extension \"max fragment length\" (id=1), len=$extension_len\n" ;;
                    0002) tls_extensions+="TLS server extension \"client certificate URL\" (id=2), len=$extension_len\n" ;;
                    0003) tls_extensions+="TLS server extension \"trusted CA keys\" (id=3, len=$extension_len\n)" ;;
                    0004) tls_extensions+="TLS server extension \"truncated HMAC\" (id=4), len=$extension_len\n" ;;
                    0005) tls_extensions+="TLS server extension \"status request\" (id=5), len=$extension_len\n"
                          if [[ $extension_len -gt 0 ]] && [[ "$process_full" =~ all ]]; then
                               # In TLSv1.3 the status_request extension contains the CertificateStatus message, unlike
                               # TLSv1.2 and below where CertificateStatus appears in its own handshake message. So, if
                               # the status_request extension is not empty, extract the value and place it in
                               # $tls_certificate_status_ascii.
                               tls_certificate_status_ascii_len=$extension_len
                               offset=$((extns_offset+12+i))
                               tls_certificate_status_ascii="${tls_serverhello_ascii:offset:tls_certificate_status_ascii_len}"
                          fi
                          ;;
                    0006) tls_extensions+="TLS server extension \"user mapping\" (id=6), len=$extension_len\n" ;;
                    0007) tls_extensions+="TLS server extension \"client authz\" (id=7), len=$extension_len\n" ;;
                    0008) tls_extensions+="TLS server extension \"server authz\" (id=8), len=$extension_len\n" ;;
                    0009) tls_extensions+="TLS server extension \"cert type\" (id=9), len=$extension_len\n" ;;
                    000A) tls_extensions+="TLS server extension \"supported_groups\" (id=10), len=$extension_len\n"
                          if [[ "$process_full" =~ all ]]; then
                               if [[ $extension_len -lt 4 ]]; then
                                    debugme tmln_warning "Malformed supported groups extension."
                                    return 1
                               fi
                               echo -n "Supported groups: " >> $TMPFILE
                               offset=$((extns_offset+12+i))
                               len1=2*$(hex2dec "${tls_serverhello_ascii:offset:4}")
                               if [[ $extension_len -lt $len1+4 ]] || [[ $len1 -lt 4 ]]; then
                                    debugme tmln_warning "Malformed supported groups extension."
                                    return 1
                               fi
                               offset=$((offset+4))
                               for (( j=0; j < len1; j+=4 )); do
                                    [[ $j -ne 0 ]] && echo -n ", " >> $TMPFILE
                                    case "${tls_serverhello_ascii:offset:4}" in
                                         "0017") echo -n "secp256r1" >> $TMPFILE ;;
                                         "0018") echo -n "secp384r1" >> $TMPFILE ;;
                                         "0019") echo -n "secp521r1" >> $TMPFILE ;;
                                         "001D") echo -n "X25519" >> $TMPFILE ;;
                                         "001E") echo -n "X448" >> $TMPFILE ;;
                                         "0100") echo -n "ffdhe2048" >> $TMPFILE ;;
                                         "0101") echo -n "ffdhe3072" >> $TMPFILE ;;
                                         "0102") echo -n "ffdhe4096" >> $TMPFILE ;;
                                         "0103") echo -n "ffdhe6144" >> $TMPFILE ;;
                                         "0104") echo -n "ffdhe8192" >> $TMPFILE ;;
                                              *) echo -n "unknown (${tls_serverhello_ascii:offset:4})" >> $TMPFILE ;;
                                    esac
                                    offset=$((offset+4))
                               done
                               echo "" >> $TMPFILE
                          fi
                          ;;
                    000B) tls_extensions+="TLS server extension \"EC point formats\" (id=11), len=$extension_len\n" ;;
                    000C) tls_extensions+="TLS server extension \"SRP\" (id=12), len=$extension_len\n" ;;
                    000D) tls_extensions+="TLS server extension \"signature algorithms\" (id=13), len=$extension_len\n" ;;
                    000E) tls_extensions+="TLS server extension \"use SRTP\" (id=14), len=$extension_len\n" ;;
                    000F) tls_extensions+="TLS server extension \"heartbeat\" (id=15), len=$extension_len\n" ;;
                    0010) tls_extensions+="TLS server extension \"application layer protocol negotiation\" (id=16), len=$extension_len\n"
                          if [[ "$process_full" =~ all ]]; then
                               if [[ $extension_len -lt 4 ]]; then
                                    debugme echo "Malformed application layer protocol negotiation extension."
                                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                                    return 1
                               fi
                               echo -n "ALPN protocol:  " >> $TMPFILE
                               offset=$((extns_offset+12+i))
                               j=2*$(hex2dec "${tls_serverhello_ascii:offset:4}")
                               if [[ $extension_len -ne $j+4 ]] || [[ $j -lt 2 ]]; then
                                    debugme echo "Malformed application layer protocol negotiation extension."
                                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                                    return 1
                               fi
                               offset=$((offset+4))
                               j=2*$(hex2dec "${tls_serverhello_ascii:offset:2}")
                               if [[ $extension_len -ne $j+6 ]]; then
                                    debugme echo "Malformed application layer protocol negotiation extension."
                                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                                    return 1
                               fi
                               offset=$((offset+2))
                               asciihex_to_binary "${tls_serverhello_ascii:offset:j}" >> "$TMPFILE"
                               echo "" >> $TMPFILE
                               echo "===============================================================================" >> $TMPFILE
                          fi
                          ;;
                    0011) tls_extensions+="TLS server extension \"certificate status version 2\" (id=17), len=$extension_len\n" ;;
                    0012) tls_extensions+="TLS server extension \"signed certificate timestamps\" (id=18), len=$extension_len\n" ;;
                    0013) tls_extensions+="TLS server extension \"client certificate type\" (id=19), len=$extension_len\n" ;;
                    0014) tls_extensions+="TLS server extension \"server certificate type\" (id=20), len=$extension_len\n" ;;
                    0015) tls_extensions+="TLS server extension \"TLS padding\" (id=21), len=$extension_len\n" ;;
                    0016) tls_extensions+="TLS server extension \"encrypt-then-mac\" (id=22), len=$extension_len\n" ;;
                    0017) tls_extensions+="TLS server extension \"extended master secret\" (id=23), len=$extension_len\n" ;;
                    0018) tls_extensions+="TLS server extension \"token binding\" (id=24), len=$extension_len\n" ;;
                    0019) tls_extensions+="TLS server extension \"cached info\" (id=25), len=$extension_len\n" ;;
                    0023) tls_extensions+="TLS server extension \"session ticket\" (id=35), len=$extension_len\n" ;;
                    0028|0033)
                          # The key share extension was renumbered from 40 to 51 in TLSv1.3 draft 23 since a few
                          # implementations have been using 40 for the extended_random extension. Since the
                          # server's version may not yet have been determined, assume that both values represent the
                          # key share extension.
                          if [[ "$extension_type" == "00$KEY_SHARE_EXTN_NR" ]]; then
                               tls_extensions+="TLS server extension \"key share\""
                          else
                               tls_extensions+="TLS server extension \"unrecognized extension\""
                          fi
                          if [[ "$extension_type" == 0028 ]]; then
                               tls_extensions+=" (id=40), len=$extension_len\n"
                          else
                               tls_extensions+=" (id=51), len=$extension_len\n"
                          fi
                          if [[ "$process_full" =~ all ]] || [[ "$process_full" == ephemeralkey ]]; then
                               if [[ $extension_len -lt 4  ]]; then
                                    debugme tmln_warning "Malformed key share extension."
                                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                                    return 1
                               fi
                               offset=$((extns_offset+12+i))
                               named_curve=$(hex2dec "${tls_serverhello_ascii:offset:4}")
                               offset=$((extns_offset+16+i))
                               msg_len=2*"$(hex2dec "${tls_serverhello_ascii:offset:4}")"
                               if [[ $msg_len -ne $extension_len-8 ]]; then
                                    debugme tmln_warning "Malformed key share extension."
                                    [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                                    return 1
                               fi
                               case $named_curve in
                                    21) dh_bits=224 ; named_curve_str="P-224" ; named_curve_oid="06052b81040021" ;;
                                    23) dh_bits=256 ; named_curve_str="P-256" ; named_curve_oid="06082a8648ce3d030107" ;;
                                    24) dh_bits=384 ; named_curve_str="P-384" ; named_curve_oid="06052b81040022" ;;
                                    25) dh_bits=521 ; named_curve_str="P-521" ; named_curve_oid="06052b81040023" ;;
                                    29) dh_bits=253 ; named_curve_str="X25519" ;;
                                    30) dh_bits=448 ; named_curve_str="X448" ;;
                                    256) dh_bits=2048 ; named_curve_str="ffdhe2048" ;;
                                    257) dh_bits=3072 ; named_curve_str="ffdhe3072" ;;
                                    258) dh_bits=4096 ; named_curve_str="ffdhe4096" ;;
                                    259) dh_bits=6144 ; named_curve_str="ffdhe6144" ;;
                                    260) dh_bits=8192 ; named_curve_str="ffdhe8192" ;;
                                    *) named_curve_str="" ; named_curve_oid="" ;;
                               esac
                               offset=$((extns_offset+20+i))
                               if ! "$HAS_PKEY"; then
                                    # The key can't be extracted without the pkey utility.
                                    key_bitstring=""
                               elif [[ $named_curve -eq 29 ]]; then
                                    key_bitstring="302a300506032b656e032100${tls_serverhello_ascii:offset:msg_len}"
                               elif [[ $named_curve -eq 30 ]]; then
                                    key_bitstring="3042300506032b656f033900${tls_serverhello_ascii:offset:msg_len}"
                               elif [[ $named_curve -lt 256 ]] && [[ -n "$named_curve_oid" ]]; then
                                    len1="$(printf "%02x" $((msg_len/2+1)))"
                                    [[ "0x${len1}" -ge "0x80" ]] && len1="81${len1}"
                                    key_bitstring="03${len1}00${tls_serverhello_ascii:offset:msg_len}"
                                    len2="$(printf "%02x" $((${#named_curve_oid}/2+9)))"
                                    len3="$(printf "%02x" $((${#named_curve_oid}/2+${#key_bitstring}/2+11)))"
                                    [[ "0x${len3}" -ge "0x80" ]] && len3="81${len3}"
                                    key_bitstring="30${len3}30${len2}06072a8648ce3d0201${named_curve_oid}${key_bitstring}"
                               elif [[ "$named_curve_str" =~ "ffdhe" ]] && [[ "${TLS13_KEY_SHARES[named_curve]}" =~ "BEGIN" ]]; then
                                    dh_param="$($OPENSSL pkey -pubout -outform DER 2>>$ERRFILE <<< "${TLS13_KEY_SHARES[named_curve]}" | hexdump -v -e '16/1 "%02X"')"

                                    # First is the length of the public-key SEQUENCE, and it is always encoded in four bytes (3082xxxx)
                                    # Next is the length of the parameters SEQUENCE, and it is also always encoded in four bytes (3082xxxx)
                                    dh_param_len=8+2*"$(hex2dec "${dh_param:12:4}")"
                                    dh_param="${dh_param:8:dh_param_len}"
                                    if [[ "0x${tls_serverhello_ascii:offset:2}" -ge 0x80 ]]; then
                                         key_bitstring="00${tls_serverhello_ascii:offset:msg_len}"
                                         msg_len+=2
                                    else
                                         key_bitstring="${tls_serverhello_ascii:offset:msg_len}"
                                    fi
                                    len1="$(printf "%04x" $((msg_len/2)))"
                                    key_bitstring="0282${len1}$key_bitstring"
                                    len1="$(printf "%04x" $((${#key_bitstring}/2+1)))"
                                    key_bitstring="${dh_param}0382${len1}00$key_bitstring"
                                    len1="$(printf "%04x" $((${#key_bitstring}/2)))"
                                    key_bitstring="3082${len1}$key_bitstring"
                               fi
                               if [[ -n "$key_bitstring" ]]; then
                                    key_bitstring="$(asciihex_to_binary "$key_bitstring" | $OPENSSL pkey -pubin -inform DER 2>$ERRFILE)"
                                    if [[ -z "$key_bitstring" ]] && [[ $DEBUG -ge 2 ]]; then
                                         if [[ -n "$named_curve_str" ]]; then
                                              prln_warning "Your $OPENSSL doesn't support $named_curve_str"
                                         else
                                              prln_warning "Your $OPENSSL doesn't support named curve $named_curve"
                                         fi
                                    fi
                               fi
                          fi
                          ;;
                    0029) tls_extensions+="TLS server extension \"pre-shared key\" (id=41), len=$extension_len\n" ;;
                    002A) tls_extensions+="TLS server extension \"early data\" (id=42), len=$extension_len\n" ;;
                    002B) tls_extensions+="TLS server extension \"supported versions\" (id=43), len=$extension_len\n"
                          if [[ $extension_len -ne 4 ]]; then
                               debugme tmln_warning "Malformed supported versions extension."
                               return 1
                          fi
                          offset=$((extns_offset+12+i))
                          tls_protocol2="${tls_serverhello_ascii:offset:4}"
                          DETECTED_TLS_VERSION="$tls_protocol2"
                          [[ "${DETECTED_TLS_VERSION:0:2}" == 7F ]] && DETECTED_TLS_VERSION="0304"
                          ;;
                    002C) tls_extensions+="TLS server extension \"cookie\" (id=44), len=$extension_len\n" ;;
                    002D) tls_extensions+="TLS server extension \"psk key exchange modes\" (id=45), len=$extension_len\n" ;;
                    002E) tls_extensions+="TLS server extension \"ticket early data info\" (id=46), len=$extension_len\n" ;;
                    002F) tls_extensions+="TLS server extension \"certificate authorities\" (id=47), len=$extension_len\n" ;;
                    0030) tls_extensions+="TLS server extension \"oid filters\" (id=48), len=$extension_len\n" ;;
                    0031) tls_extensions+="TLS server extension \"post handshake auth\" (id=49), len=$extension_len\n" ;;
                    3374) tls_extensions+="TLS server extension \"next protocol\" (id=13172), len=$extension_len\n"
                          if [[ "$process_full" =~ all ]]; then
                               local -i protocol_len
                               echo -n "Protocols advertised by server: " >> $TMPFILE
                               offset=$((extns_offset+12+i))
                               for (( j=0; j<extension_len; j+=protocol_len+2 )); do
                                    if [[ $extension_len -lt $j+2 ]]; then
                                         debugme echo "Malformed next protocol extension."
                                         [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                                         return 1
                                    fi
                                    protocol_len=2*$(hex2dec "${tls_serverhello_ascii:offset:2}")
                                    if [[ $extension_len -lt $j+$protocol_len+2 ]]; then
                                         debugme echo "Malformed next protocol extension."
                                         [[ $DEBUG -ge 1 ]] && tmpfile_handle ${FUNCNAME[0]}.txt
                                         return 1
                                    fi
                                    offset=$((offset+2))
                                    asciihex_to_binary "${tls_serverhello_ascii:offset:protocol_len}" >> "$TMPFILE"
                                    offset=$((offset+protocol_len))
                                    [[ $j+$protocol_len+2 -lt $extension_len ]] && echo -n ", " >> $TMPFILE
                               done
                               echo "" >> $TMPFILE
                               echo "===============================================================================" >> $TMPFILE
                          fi
                          ;;
                    FF01) tls_extensions+="TLS server extension \"renegotiation info\" (id=65281), len=$extension_len\n" ;;
                       *) tls_extensions+="TLS server extension \"unrecognized extension\" (id=$(printf "%d\n\n" "0x$extension_type")), len=$extension_len\n" ;;
               esac
               # After processing all of the extensions in the ServerHello message,
               # if it has been determined that the response is TLSv1.3 and the
               # response was decrypted, then modify $tls_serverhello_ascii by adding
               # the extensions from the EncryptedExtensions and Certificate messages
               # and then process them.
               if ! "$added_encrypted_extensions" && [[ "$DETECTED_TLS_VERSION" == "0304" ]] && \
                  [[ $((i+8+extension_len)) -eq $tls_extensions_len ]]; then
                    # Note that the encrypted extensions have been added so that
                    # the aren't added a second time.
                    added_encrypted_extensions=true
                    if [[ -n "$tls_encryptedextensions_ascii" ]]; then
                         tls_serverhello_ascii_len+=$tls_encryptedextensions_ascii_len-4
                         tls_extensions_len+=$tls_encryptedextensions_ascii_len-4
                         tls_encryptedextensions_ascii_len=$tls_encryptedextensions_ascii_len/2-2
                         offset=$((extns_offset+4))
                         tls_serverhello_ascii="${tls_serverhello_ascii:0:extns_offset}$(printf "%04X" $((0x${tls_serverhello_ascii:extns_offset:4}+$tls_encryptedextensions_ascii_len)))${tls_serverhello_ascii:offset}${tls_encryptedextensions_ascii:4}"
                    fi
                    if [[ -n "$tls_certificate_ascii" ]]; then
                         # In TLS 1.3, the Certificate message begins with a zero length certificate_request_context.
                         # In addition, certificate_list is now a list of (certificate, extension) pairs rather than
                         # just certificates. So, extract the extensions and add them to $tls_serverhello_ascii and
                         # create a new $tls_certificate_ascii that only contains a list of certificates.
                         if [[ -n "$tls_certificate_ascii" ]]; then
                              if [[ "${tls_certificate_ascii:0:2}" != "00" ]]; then
                                   debugme tmln_warning "Malformed Certificate Handshake message in ServerHello."
                                   tmpfile_handle ${FUNCNAME[0]}.txt
                                   return 1
                              fi
                              if [[ $tls_certificate_ascii_len -lt 8 ]]; then
                                   debugme tmln_warning "Malformed Certificate Handshake message in ServerHello."
                                   tmpfile_handle ${FUNCNAME[0]}.txt
                                   return 1
                              fi
                              certificate_list_len=2*$(hex2dec "${tls_certificate_ascii:2:6}")
                              if [[ $certificate_list_len -ne $tls_certificate_ascii_len-8 ]]; then
                                   debugme tmln_warning "Malformed Certificate Handshake message in ServerHello."
                                   tmpfile_handle ${FUNCNAME[0]}.txt
                                   return 1
                              fi
                              for (( j=8; j < tls_certificate_ascii_len; j+=extn_len )); do
                                   if [[ $tls_certificate_ascii_len-$j -lt 6 ]]; then
                                        debugme tmln_warning "Malformed Certificate Handshake message in ServerHello."
                                        tmpfile_handle ${FUNCNAME[0]}.txt
                                        return 1
                                   fi
                                   certificate_len=2*$(hex2dec "${tls_certificate_ascii:j:6}")
                                   if [[ $certificate_len -gt $tls_certificate_ascii_len-$j-6 ]]; then
                                        debugme tmln_warning "Malformed Certificate Handshake message in ServerHello."
                                        tmpfile_handle ${FUNCNAME[0]}.txt
                                        return 1
                                   fi
                                   len1=$certificate_len+6
                                   tls_revised_certificate_msg+="${tls_certificate_ascii:j:len1}"
                                   j+=$len1
                                   extn_len=2*$(hex2dec "${tls_certificate_ascii:j:4}")
                                   j+=4
                                   # TODO: Should only the extensions associated with the EE certificate be added to $tls_serverhello_ascii?
                                   tls_serverhello_ascii_len+=$extn_len
                                   tls_extensions_len+=$extn_len
                                   offset=$((extns_offset+4))
                                   tls_serverhello_ascii="${tls_serverhello_ascii:0:extns_offset}$(printf "%04X" $(( 0x${tls_serverhello_ascii:extns_offset:4}+extn_len/2)) )${tls_serverhello_ascii:offset}${tls_certificate_ascii:j:extn_len}"
                              done
                              tls_certificate_ascii_len=${#tls_revised_certificate_msg}+6
                              tls_certificate_ascii="$(printf "%06X" $(( tls_certificate_ascii_len/2-3)) )$tls_revised_certificate_msg"
                         fi
                    fi
               fi
          done
     fi

     if [[ "$DETECTED_TLS_VERSION" == "0300" ]]; then
          echo "Protocol  : SSLv3" >> $TMPFILE
     else
          echo "Protocol  : TLSv1.$((0x$DETECTED_TLS_VERSION-0x0301))" >> $TMPFILE
     fi
     echo "===============================================================================" >> $TMPFILE
     if [[ $TLS_NR_CIPHERS -ne 0 ]]; then
          if [[ "${tls_cipher_suite:0:2}" == "00" ]]; then
               rfc_cipher_suite="$(show_rfc_style "x${tls_cipher_suite:2:2}")"
          else
               rfc_cipher_suite="$(show_rfc_style "x${tls_cipher_suite:0:4}")"
          fi
     elif "$HAS_CIPHERSUITES"; then
          rfc_cipher_suite="$($OPENSSL ciphers -V -ciphersuites "$TLS13_OSSL_CIPHERS" 'ALL:COMPLEMENTOFALL' | grep -i " 0x${tls_cipher_suite:0:2},0x${tls_cipher_suite:2:2} " | awk '{ print $3 }')"
     else
          rfc_cipher_suite="$($OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL' | grep -i " 0x${tls_cipher_suite:0:2},0x${tls_cipher_suite:2:2} " | awk '{ print $3 }')"
     fi
     echo "Cipher    : $rfc_cipher_suite" >> $TMPFILE
     if [[ $dh_bits -ne 0 ]]; then
          if [[ "$named_curve_str" =~ "ffdhe" ]]; then
               echo "Server Temp Key: DH, $named_curve_str, $dh_bits bits" >> $TMPFILE
          elif [[ "$named_curve_str" == "X25519" ]] || [[ "$named_curve_str" == "X448" ]]; then
               echo "Server Temp Key: $named_curve_str, $dh_bits bits" >> $TMPFILE
          else
               echo "Server Temp Key: ECDH, $named_curve_str, $dh_bits bits" >> $TMPFILE
          fi
     fi
     if [[ -n "$key_bitstring" ]]; then
          echo "$key_bitstring" >> $TMPFILE
          [[ "${TLS13_KEY_SHARES[named_curve]}" =~ "BEGIN" ]] && \
               echo "${TLS13_KEY_SHARES[named_curve]}" >> $TMPFILE
     fi
     echo "===============================================================================" >> $TMPFILE
     if [[ "0x${DETECTED_TLS_VERSION:2:2}" -le "0x03" ]]; then
          case $tls_compression_method in
               00) echo "Compression: NONE" >> $TMPFILE ;;
               01) echo "Compression: zlib compression" >> $TMPFILE ;;
               40) echo "Compression: LZS compression" >> $TMPFILE ;;
                *) echo "Compression: unrecognized compression method" >> $TMPFILE ;;
          esac
          echo "===============================================================================" >> $TMPFILE
     fi
     [[ -n "$tls_extensions" ]] && echo -e "$tls_extensions" >> $TMPFILE

     if [[ $DEBUG -ge 3 ]]; then
          echo "TLS server hello message:"
          if [[ $DEBUG -ge 4 ]]; then
               echo "     tls_protocol:           0x$tls_protocol2"
               [[ "0x${DETECTED_TLS_VERSION:2:2}" -le "0x03" ]] && echo "     tls_sid_len:            0x$tls_sid_len_hex / = $((tls_sid_len/2))"
          fi
          if [[ "0x${DETECTED_TLS_VERSION:2:2}" -le "0x03" ]]; then
               echo -n "     tls_hello_time:         0x$tls_hello_time "
               parse_date "$TLS_TIME" "+%Y-%m-%d %r" "%s"                  # in debugging mode we don't mind the cycles and don't use TLS_DIFFTIME_SET
          fi
          echo -n "     tls_cipher_suite:       0x$tls_cipher_suite"
          if [[ -n "$rfc_cipher_suite" ]]; then
               echo " ($rfc_cipher_suite)"
          else
               echo ""
          fi
          if [[ $dh_bits -ne 0 ]]; then
               if [[ "$named_curve_str" =~ "ffdhe" ]]; then
                    echo "     dh_bits:                DH, $named_curve_str, $dh_bits bits"
               elif [[ "$named_curve_str" == "X25519" ]] || [[ "$named_curve_str" == "X448" ]]; then
                    echo "     dh_bits:                $named_curve_str, $dh_bits bits"
               else
                    echo "     dh_bits:                ECDH, $named_curve_str, $dh_bits bits"
               fi
          fi
          if [[ "0x${DETECTED_TLS_VERSION:2:2}" -le "0x03" ]]; then
               echo -n "     tls_compression_method: 0x$tls_compression_method "
               case $tls_compression_method in
                    00) echo "(NONE)" ;;
                    01) echo "(zlib compression)" ;;
                    40) echo "(LZS compression)" ;;
                     *) echo "(unrecognized compression method)" ;;
               esac
          fi
          if [[ -n "$tls_extensions" ]]; then
               echo -n "     tls_extensions: "
               newline_to_spaces "$(grep -a 'TLS server extension ' $TMPFILE | \
                    sed -e 's/TLS server extension //g' -e 's/\" (id=/\/#/g' \
                        -e 's/,.*$/,/g' -e 's/),$/\"/g' \
                        -e 's/elliptic curves\/#10/supported_groups\/#10/g')"
               echo ""
               if [[ "$tls_extensions" =~ supported_groups ]]; then
                    echo "     Supported Groups:       $(grep "Supported groups:" "$TMPFILE" | sed 's/Supported groups: //')"
               fi
               if [[ "$tls_extensions" =~ application\ layer\ protocol\ negotiation ]]; then
                    echo "     ALPN protocol:          $(grep "ALPN protocol:" "$TMPFILE" | sed 's/ALPN protocol:  //')"
               fi
               if [[ "$tls_extensions" =~ next\ protocol ]]; then
                    echo "     NPN protocols:          $(grep "Protocols advertised by server:" "$TMPFILE" | sed 's/Protocols advertised by server: //')"
               fi
          fi
          tmln_out
     fi

     # If a CIPHER_SUITES string was provided, then check that $tls_cipher_suite is in the string.
     # this appeared in yassl + MySQL (https://github.com/drwetter/testssl.sh/pull/784) but adds robustness
     # to the implementation
     if [[ -n "$cipherlist" ]]; then
          tls_cipher_suite="$(tolower "$tls_cipher_suite")"
          tls_cipher_suite="${tls_cipher_suite:0:2}\\x${tls_cipher_suite:2:2}"
          cipherlist_len=${#cipherlist}
          for (( i=0; i < cipherlist_len; i+=8 )); do
               # At the right hand side we need the quotes here!
               [[ "${cipherlist:i:6}" == "$tls_cipher_suite" ]] && break
          done
          if [[ $i -ge $cipherlist_len ]]; then
               BAD_SERVER_HELLO_CIPHER=true
               debugme echo "The ServerHello specifies a cipher suite that wasn't included in the ClientHello."
               tmpfile_handle ${FUNCNAME[0]}.txt
               return 1
          fi
     fi

     # If the ClientHello included a supported_versions extension, then check that the
     # $DETECTED_TLS_VERSION appeared in the list offered in the ClientHello.
     if [[ "${TLS_CLIENT_HELLO:0:2}" == 01 ]]; then
          # get position of cipher lists (just after session id)
          offset=78+2*$(hex2dec "${TLS_CLIENT_HELLO:76:2}")
          # get position of compression methods
          offset+=4+2*$(hex2dec "${TLS_CLIENT_HELLO:offset:4}")
          # get position of extensions
          extns_offset=$offset+6+2*$(hex2dec "${TLS_CLIENT_HELLO:offset:2}")
          len1=${#TLS_CLIENT_HELLO}
          for (( i=extns_offset; i < len1; i+=8+extension_len )); do
               extension_type="${TLS_CLIENT_HELLO:i:4}"
               offset=4+$i
               extension_len=2*$(hex2dec "${TLS_CLIENT_HELLO:offset:4}")
               if [[ "$extension_type" == 002b ]]; then
                    offset+=6
                    tls_protocol2="$(tolower "$tls_protocol2")"
                    for (( j=0; j < extension_len-2; j+=4 )); do
                         [[ "${TLS_CLIENT_HELLO:offset:4}" == $tls_protocol2 ]] && break
                         offset+=4
                    done
                    if [[ $j -eq $extension_len-2 ]]; then
                         debugme echo "The ServerHello specifies a version that wasn't offered in the ClientHello."
                         tmpfile_handle ${FUNCNAME[0]}.txt
                         return 1
                    fi
                    break
               fi
          done
     fi

     # Now parse the Certificate message.
     if [[ "$process_full" =~ all ]]; then
          # not sure why we need this
          [[ -e "$HOSTCERT" ]] && rm "$HOSTCERT"
          [[ -e "$TEMPDIR/intermediatecerts.pem" ]] && > "$TEMPDIR/intermediatecerts.pem"
     fi
     if [[ $tls_certificate_ascii_len -ne 0 ]]; then
          # The first certificate is the server's certificate. If there are anything
          # subsequent certificates, they are intermediate certificates.
          if [[ $tls_certificate_ascii_len -lt 12 ]]; then
               debugme echo "Malformed Certificate Handshake message in ServerHello."
               tmpfile_handle ${FUNCNAME[0]}.txt
               return 1
          fi
          certificate_list_len=2*$(hex2dec "${tls_certificate_ascii:0:6}")
          if [[ $certificate_list_len -ne $tls_certificate_ascii_len-6 ]]; then
               debugme echo "Malformed Certificate Handshake message in ServerHello."
               tmpfile_handle ${FUNCNAME[0]}.txt
               return 1
          fi

          # Place server's certificate in $HOSTCERT
          certificate_len=2*$(hex2dec "${tls_certificate_ascii:6:6}")
          if [[ $certificate_len -gt $tls_certificate_ascii_len-12 ]]; then
               debugme echo "Malformed Certificate Handshake message in ServerHello."
               tmpfile_handle ${FUNCNAME[0]}.txt
               return 1
          fi
          asciihex_to_binary "${tls_certificate_ascii:12:certificate_len}" | \
               $OPENSSL x509 -inform DER -outform PEM -out "$HOSTCERT" 2>$ERRFILE
          if [[ $? -ne 0 ]]; then
               debugme echo "Malformed certificate in Certificate Handshake message in ServerHello."
               tmpfile_handle ${FUNCNAME[0]}.txt
               return 1
          fi
          get_pub_key_size
          echo "===============================================================================" >> $TMPFILE
          echo "---" >> $TMPFILE
          echo "Certificate chain" >> $TMPFILE
          subjectDN="$($OPENSSL x509 -in $HOSTCERT -noout -subject 2>>$ERRFILE)"
          issuerDN="$($OPENSSL x509 -in $HOSTCERT -noout -issuer 2>>$ERRFILE)"
          echo " $nr_certs s:${subjectDN:9}" >> $TMPFILE
          echo "   i:${issuerDN:8}" >> $TMPFILE
          cat "$HOSTCERT" >> $TMPFILE

          echo "" > "$TEMPDIR/intermediatecerts.pem"
          # Place any additional certificates in $TEMPDIR/intermediatecerts.pem
          CERTIFICATE_LIST_ORDERING_PROBLEM=false
          CAissuerDN="$issuerDN"
          for (( i=12+certificate_len; i<tls_certificate_ascii_len; i+=certificate_len )); do
               if [[ $tls_certificate_ascii_len-$i -lt 6 ]]; then
                    debugme echo "Malformed Certificate Handshake message in ServerHello."
                    tmpfile_handle ${FUNCNAME[0]}.txt
                    return 1
               fi
               certificate_len=2*$(hex2dec "${tls_certificate_ascii:i:6}")
               i+=6
               if [[ $certificate_len -gt $tls_certificate_ascii_len-$i ]]; then
                    debugme echo "Malformed certificate in Certificate Handshake message in ServerHello."
                    tmpfile_handle ${FUNCNAME[0]}.txt
                    return 1
               fi
               pem_certificate="$(asciihex_to_binary "${tls_certificate_ascii:i:certificate_len}" | \
                                  $OPENSSL x509 -inform DER -outform PEM 2>$ERRFILE)"
               if [[ $? -ne 0 ]]; then
                    debugme echo "Malformed certificate in Certificate Handshake message in ServerHello."
                    tmpfile_handle ${FUNCNAME[0]}.txt
                    return 1
               fi
               nr_certs+=1
               CAsubjectDN="$($OPENSSL x509 -noout -subject 2>>$ERRFILE <<< "$pem_certificate")"
               # Check that this certificate certifies the one immediately preceding it.
               [[ "${CAsubjectDN:9}" != "${CAissuerDN:8}" ]] && CERTIFICATE_LIST_ORDERING_PROBLEM=true
               CAissuerDN="$($OPENSSL x509 -noout -issuer 2>>$ERRFILE <<< "$pem_certificate")"
               echo " $nr_certs s:${CAsubjectDN:9}" >> $TMPFILE
               echo "   i:${CAissuerDN:8}" >> $TMPFILE
               echo "$pem_certificate"  >> $TMPFILE
               echo "$pem_certificate" >> "$TEMPDIR/intermediatecerts.pem"
               if [[ -z "$hostcert_issuer" ]] && [[ "${CAsubjectDN:9}" == "${issuerDN:8}" ]]; then
                    # The issuer's certificate is needed if there is a stapled OCSP response,
                    # and it may be needed if check_revocation_ocsp() will later be called
                    # with the OCSP URI in the server's certificate.
                    hostcert_issuer="$TEMPDIR/hostcert_issuer.pem"
                    echo "$pem_certificate" > "$hostcert_issuer"
               fi
          done
          echo "---" >> $TMPFILE
          echo "Server certificate" >> $TMPFILE
          echo "subject=${subjectDN:9}" >> $TMPFILE
          echo "issuer=${issuerDN:8}" >> $TMPFILE
          echo "---" >> $TMPFILE
     fi

     # Now parse the certificate status message
     if [[ $tls_certificate_status_ascii_len -ne 0 ]] && [[ $tls_certificate_status_ascii_len -lt 8 ]]; then
          debugme echo "Malformed certificate status Handshake message in ServerHello."
          tmpfile_handle ${FUNCNAME[0]}.txt
          return 1
     elif [[ $tls_certificate_status_ascii_len -ne 0 ]] && [[ "${tls_certificate_status_ascii:0:2}" == "01" ]]; then
          # This is a certificate status message of type "ocsp"
          ocsp_response_len=2*$(hex2dec "${tls_certificate_status_ascii:2:6}")
          if [[ $ocsp_response_len -ne $tls_certificate_status_ascii_len-8 ]]; then
               debugme echo "Malformed certificate status Handshake message in ServerHello."
               tmpfile_handle ${FUNCNAME[0]}.txt
               return 1
          fi
          ocsp_resp_offset=8
     elif [[ $tls_certificate_status_ascii_len -ne 0 ]] && [[ "${tls_certificate_status_ascii:0:2}" == "02" ]]; then
          # This is a list of OCSP responses, but only the first one is needed
          # since the first one corresponds to the server's certificate.
          ocsp_response_list_len=2*$(hex2dec "${tls_certificate_status_ascii:2:6}")
          if [[ $ocsp_response_list_len -ne $tls_certificate_status_ascii_len-8 ]] || [[ $ocsp_response_list_len -lt 6 ]]; then
               debugme echo "Malformed certificate status Handshake message in ServerHello."
               tmpfile_handle ${FUNCNAME[0]}.txt
               return 1
          fi
          ocsp_response_len=2*$(hex2dec "${tls_certificate_status_ascii:8:6}")
          if [[ $ocsp_response_len -gt $ocsp_response_list_len-6 ]]; then
               debugme echo "Malformed certificate status Handshake message in ServerHello."
               tmpfile_handle ${FUNCNAME[0]}.txt
               return 1
          fi
          ocsp_resp_offset=14
     fi
     STAPLED_OCSP_RESPONSE=""
     if [[ $ocsp_response_len -ne 0 ]]; then
          STAPLED_OCSP_RESPONSE="${tls_certificate_status_ascii:ocsp_resp_offset:ocsp_response_len}"
          echo "OCSP response:" >> $TMPFILE
          echo "===============================================================================" >> $TMPFILE
          if [[ -n "$hostcert_issuer" ]]; then
               asciihex_to_binary "$STAPLED_OCSP_RESPONSE" | \
                    $OPENSSL ocsp -no_nonce -CAfile $TEMPDIR/intermediatecerts.pem -issuer $hostcert_issuer -cert $HOSTCERT -respin /dev/stdin -resp_text >> $TMPFILE 2>$ERRFILE
          else
               asciihex_to_binary "$STAPLED_OCSP_RESPONSE" | \
                    $OPENSSL ocsp -respin /dev/stdin -resp_text >> $TMPFILE 2>$ERRFILE
          fi
          echo "===============================================================================" >> $TMPFILE
     elif [[ "$process_full" =~ all ]]; then
          echo "OCSP response: no response sent" >> $TMPFILE
          echo "===============================================================================" >> $TMPFILE
     fi

     # Now parse the server key exchange message
     if [[ $tls_serverkeyexchange_ascii_len -ne 0 ]]; then
          if [[ $rfc_cipher_suite =~ TLS_ECDHE_ ]] || [[ $rfc_cipher_suite =~ TLS_ECDH_anon ]] || \
             [[ $rfc_cipher_suite == ECDHE* ]] || [[ $rfc_cipher_suite == AECDH* ]]; then
               if [[ $tls_serverkeyexchange_ascii_len -lt 6 ]]; then
                    debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello."
                    tmpfile_handle ${FUNCNAME[0]}.txt
                    return 1
               fi
               curve_type=$(hex2dec "${tls_serverkeyexchange_ascii:0:2}")
               if [[ $curve_type -eq 3 ]]; then
                    # named_curve - the curve is identified by a 2-byte number
                    named_curve=$(hex2dec "${tls_serverkeyexchange_ascii:2:4}")
                    # https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8
                    case $named_curve in
                         1) dh_bits=163 ; named_curve_str="K-163" ;;
                         2) dh_bits=162 ; named_curve_str="sect163r1" ;;
                         3) dh_bits=163 ; named_curve_str="B-163" ;;
                         4) dh_bits=193 ; named_curve_str="sect193r1" ;;
                         5) dh_bits=193 ; named_curve_str="sect193r2" ;;
                         6) dh_bits=232 ; named_curve_str="K-233" ;;
                         7) dh_bits=233 ; named_curve_str="B-233" ;;
                         8) dh_bits=238 ; named_curve_str="sect239k1" ;;
                         9) dh_bits=281 ; named_curve_str="K-283" ;;
                         10) dh_bits=282 ; named_curve_str="B-283" ;;
                         11) dh_bits=407 ; named_curve_str="K-409" ;;
                         12) dh_bits=409 ; named_curve_str="B-409" ;;
                         13) dh_bits=570 ; named_curve_str="K-571" ;;
                         14) dh_bits=570 ; named_curve_str="B-571" ;;
                         15) dh_bits=161 ; named_curve_str="secp160k1" ;;
                         16) dh_bits=161 ; named_curve_str="secp160r1" ;;
                         17) dh_bits=161 ; named_curve_str="secp160r2" ;;
                         18) dh_bits=192 ; named_curve_str="secp192k1" ;;
                         19) dh_bits=192 ; named_curve_str="P-192" ;;
                         20) dh_bits=225 ; named_curve_str="secp224k1" ;;
                         21) dh_bits=224 ; named_curve_str="P-224" ;;
                         22) dh_bits=256 ; named_curve_str="secp256k1" ;;
                         23) dh_bits=256 ; named_curve_str="P-256" ;;
                         24) dh_bits=384 ; named_curve_str="P-384" ;;
                         25) dh_bits=521 ; named_curve_str="P-521" ;;
                         26) dh_bits=256 ; named_curve_str="brainpoolP256r1" ;;
                         27) dh_bits=384 ; named_curve_str="brainpoolP384r1" ;;
                         28) dh_bits=512 ; named_curve_str="brainpoolP512r1" ;;
                         29) dh_bits=253 ; named_curve_str="X25519" ;;
                         30) dh_bits=448 ; named_curve_str="X448" ;;
                    esac
               fi
               if [[ $dh_bits -ne 0 ]] && [[ $named_curve -ne 29 ]] && [[ $named_curve -ne 30 ]]; then
                    [[ $DEBUG -ge 3 ]] && echo -e "     dh_bits:                ECDH, $named_curve_str, $dh_bits bits\n"
                    echo "Server Temp Key: ECDH, $named_curve_str, $dh_bits bits" >> $TMPFILE
               elif [[ $dh_bits -ne 0 ]]; then
                    [[ $DEBUG -ge 3 ]] && echo -e "     dh_bits:                $named_curve_str, $dh_bits bits\n"
                    echo "Server Temp Key: $named_curve_str, $dh_bits bits" >> $TMPFILE
               fi
          elif [[ $rfc_cipher_suite =~ TLS_DHE_ ]] || [[ $rfc_cipher_suite =~ TLS_DH_anon ]] || \
               [[ $rfc_cipher_suite == "DHE-"* ]] || [[ $rfc_cipher_suite == "EDH-"* ]] || \
               [[ $rfc_cipher_suite == "EXP1024-DHE-"* ]]; then
               # For DH ephemeral keys the first field is p, and the length of
               # p is the same as the length of the public key.
               if [[ $tls_serverkeyexchange_ascii_len -lt 4 ]]; then
                    debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello."
                    tmpfile_handle ${FUNCNAME[0]}.txt
                    return 1
               fi
               dh_p_len=2*$(hex2dec "${tls_serverkeyexchange_ascii:0:4}")
               offset=4+$dh_p_len
               if [[ $tls_serverkeyexchange_ascii_len -lt $offset ]]; then
                    debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello."
                    tmpfile_handle ${FUNCNAME[0]}.txt
                    return 1
               fi

               # Subtract any leading 0 bytes
               for (( i=4; i < offset; i+=2 )); do
                    [[ "${tls_serverkeyexchange_ascii:i:2}" != "00" ]] && break
                    dh_p_len=$dh_p_len-2
               done
               if [[ $i -ge $offset ]]; then
                    debugme echo "Malformed ServerKeyExchange Handshake message in ServerHello."
                    tmpfile_handle ${FUNCNAME[0]}.txt
                    return 1
               fi
               dh_p="${tls_serverkeyexchange_ascii:i:dh_p_len}"

               dh_bits=4*$dh_p_len
               msb=$(hex2dec "${tls_serverkeyexchange_ascii:i:2}")
               for (( mask=128; msb < mask; mask/=2 )); do
                    dh_bits=$dh_bits-1
               done

               key_bitstring="$(get_dh_ephemeralkey "$tls_serverkeyexchange_ascii")"
               [[ $? -eq 0 ]] && echo "$key_bitstring" >> $TMPFILE

               # Check to see whether the ephemeral public key uses one of the groups from
               # RFC 7919 for parameters
               case $dh_bits in
                    2048) named_curve=256; named_curve_str=" ffdhe2048," ;;
                    3072) named_curve=257; named_curve_str=" ffdhe3072," ;;
                    4096) named_curve=258; named_curve_str=" ffdhe4096," ;;
                    6144) named_curve=259; named_curve_str=" ffdhe6144," ;;
                    8192) named_curve=260; named_curve_str=" ffdhe8192," ;;
                       *) named_curve=0;   named_curve_str="" ;;
               esac
               [[ -z "$key_bitstring" ]] && named_curve=0 && named_curve_str=""
               if "$HAS_PKEY" && [[ $named_curve -ne 0 ]] && [[ "${TLS13_KEY_SHARES[named_curve]}" =~ BEGIN ]]; then
                    ephemeral_param="$($OPENSSL pkey -pubin -text -noout 2>>$ERRFILE <<< "$key_bitstring" | grep -EA 1000 "prime:|prime P:")"
                    rfc7919_param="$($OPENSSL pkey -text -noout 2>>$ERRFILE <<< "${TLS13_KEY_SHARES[named_curve]}" | grep -EA 1000 "prime:|prime P:")"
                    [[ "$ephemeral_param" != "$rfc7919_param" ]] && named_curve_str=""
               fi

               [[ $DEBUG -ge 3 ]] && [[ $dh_bits -ne 0 ]] && echo -e "     dh_bits:                DH,$named_curve_str $dh_bits bits\n"
               [[ $dh_bits -ne 0 ]] && echo "Server Temp Key: DH,$named_curve_str $dh_bits bits" >> $TMPFILE
          fi
     fi
     tmpfile_handle ${FUNCNAME[0]}.txt

     TLS_SERVER_HELLO="02$(printf "%06x" $(( tls_serverhello_ascii_len/2)) )${tls_serverhello_ascii}"
     return 0
}

# ASCII-HEX encoded session ticket
parse_tls13_new_session_ticket() {
    local tls_version="$1"
    local new_session_ticket="$2"
    local -i len ticket_lifetime ticket_age_add min_len remainder
    local ticket_nonce ticket extensions
    local has_nonce=true

    [[ "${new_session_ticket:0:2}" == 04 ]] || return 7
    # Prior to draft 21 the NewSessionTicket did not include a ticket_nonce.
    [[ "${tls_version:0:2}" == 7F ]] && [[ 0x${tls_version:2:2} -le 20 ]] && has_nonce=false

    # Set min_len to the minimum length that a session ticket can be.
    min_len=28
    "$has_nonce" || min_len=$((min_len-2))

    remainder=$((2*0x${new_session_ticket:2:6}))
    [[ $remainder -ge $min_len ]] || return 7
    [[ ${#new_session_ticket} -ge $((remainder + 8)) ]] || return 7

    ticket_lifetime=0x${new_session_ticket:8:8}
    ticket_age_add=0x${new_session_ticket:16:8}
    new_session_ticket="${new_session_ticket:24}"
    remainder=$((remainder-16))

    if "$has_nonce"; then
         len=$((2*0x${new_session_ticket:0:2}))
         new_session_ticket="${new_session_ticket:2}"
         [[ $remainder -ge $((len + 12)) ]] || return 7
         ticket_nonce="${new_session_ticket:0:len}"
         new_session_ticket="${new_session_ticket:len}"
         remainder=$((remainder-len-2))
    fi

    len=$((2*0x${new_session_ticket:0:4}))
    new_session_ticket="${new_session_ticket:4}"
    [[ $remainder -ge $((len + 8)) ]] || return 7
    ticket="${new_session_ticket:0:len}"
    new_session_ticket="${new_session_ticket:len}"
    remainder=$((remainder-len-4))

    len=$((2*0x${new_session_ticket:0:4}))
    new_session_ticket="${new_session_ticket:4}"
    [[ $remainder -eq $((len + 4)) ]] || return 7
    extensions="${new_session_ticket:0:len}"

    echo "    TLS session ticket lifetime hint: $ticket_lifetime (seconds)" > $TMPFILE
    tmpfile_handle ${FUNCNAME[0]}.txt $TMPFILE
    return 0
}

#arg1 (optional): list of ciphers suites or empty
#arg2 (optional): "true" if full server response should be parsed.
# return: 6: couldn't open socket, 3(!): sslv2 handshake succeeded, 0=no SSLv2
#         1,4,6,7: see return value of parse_sslv2_serverhello()
sslv2_sockets() {
     local ret
     local client_hello cipher_suites len_client_hello
     local len_ciph_suites_byte len_ciph_suites
     local server_hello sock_reply_file2
     local -i response_len server_hello_len
     local parse_complete=false

     # this could be empty so swe use '=='
     if [[ "$2" == true ]]; then
          parse_complete=true
     fi
     if [[ -n "$1" ]]; then
          cipher_suites="$1"
     else
          cipher_suites="
          05,00,80, # 1st cipher   9 cipher specs, only classical V2 ciphers are used here, see  FIXME below
          03,00,80, # 2nd          there are v3 in v2!!! : https://tools.ietf.org/html/rfc6101#appendix-E
          01,00,80, # 3rd          Cipher specifications introduced in version 3.0 can be included in version 2.0 client hello messages using
          07,00,c0, # 4th          the syntax below. [..] # V2CipherSpec (see Version 3.0 name) = { 0x00, CipherSuite }; !!!!
          08,00,80, # 5th
          06,00,40, # 6th
          04,00,80, # 7th
          02,00,80, # 8th
          06,01,40, # 9
          07,01,c0, # 10
          FF,80,00, # 11
          FF,80,10, # 12
          00,00,00" # 13
          # FIXME: also SSLv3 ciphers, see
          # https://web.archive.org/web/20170310142840/http://max.euston.net/d/tip_sslciphers.html
     fi

     code2network "$cipher_suites" # convert CIPHER_SUITES
     cipher_suites="$NW_STR"       # we don't have the leading \x here so string length is two byte less, see next
     len_ciph_suites_byte=${#cipher_suites}

     let "len_ciph_suites_byte += 2"
     len_ciph_suites=$(printf "%02x\n" $(( len_ciph_suites_byte / 4 )))
     len_client_hello=$(printf "%02x\n" $((0x$len_ciph_suites + 0x19)))

     client_hello="
     ,80,$len_client_hello         # length
     ,01                           # Client Hello
     ,00,02                        # SSLv2
     ,00,$len_ciph_suites          # cipher spec length
     ,00,00                        # session ID length
     ,00,10                        # challenge length
     ,$cipher_suites
     ,29,22,be,b3,5a,01,8b,04,fe,5f,80,03,a0,13,eb,c4" # Challenge
     # https://idea.popcount.org/2012-06-16-dissecting-ssl-handshake/ (client)

     fd_socket 5 || return 6
     debugme echo -n "sending client hello... "
     socksend_clienthello "$client_hello"

     sockread_serverhello 32768
     if "$parse_complete"; then
          server_hello=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE")
          server_hello_len=2+$(hex2dec "${server_hello:1:3}")
          response_len=$(wc -c "$SOCK_REPLY_FILE" | awk '{ print $1 }')
          for (( 1; response_len < server_hello_len; 1 )); do
               sock_reply_file2=${SOCK_REPLY_FILE}.2
               mv "$SOCK_REPLY_FILE" "$sock_reply_file2"

               debugme echo -n "requesting more server hello data... "
               socksend "" $USLEEP_SND
               sockread_serverhello 32768

               [[ ! -s "$SOCK_REPLY_FILE" ]] && break
               cat "$SOCK_REPLY_FILE" >> "$sock_reply_file2"
               mv "$sock_reply_file2" "$SOCK_REPLY_FILE"
               response_len=$(wc -c "$SOCK_REPLY_FILE" | awk '{ print $1 }')
          done
     fi
     debugme echo "reading server hello... "
     if [[ "$DEBUG" -ge 4 ]]; then
          hexdump -C "$SOCK_REPLY_FILE" | head -6
          tmln_out
     fi

     parse_sslv2_serverhello "$SOCK_REPLY_FILE" "$parse_complete"
     ret=$?

     close_socket
     tmpfile_handle ${FUNCNAME[0]}.dd $SOCK_REPLY_FILE
     return $ret
}

# arg1: supported groups extension
# arg2: "all" - process full response (including Certificate and certificate_status handshake messages)
#       "ephemeralkey" - extract the server's ephemeral key (if any)
# Given the supported groups extension, create a key_share extension that includes a key share for
# each group listed in the supported groups extension.
generate_key_share_extension() {
     local supported_groups
     local -i i len supported_groups_len group
     local extn_len list_len
     local key_share key_shares=""
     local -i nr_key_shares=0

     supported_groups="${1//\\x/}"
     [[ "${supported_groups:0:4}" != "000a" ]] && return 1

     supported_groups_len=${#supported_groups}
     [[ $supported_groups_len -lt 16 ]] && return 1

     len=2*$(hex2dec "${supported_groups:4:4}")
     [[ $len+8 -ne $supported_groups_len ]] && return 1

     len=2*$(hex2dec "${supported_groups:8:4}")
     [[ $len+12 -ne $supported_groups_len ]] && return 1

     for (( i=12; i<supported_groups_len; i+=4 )); do
          group=$(hex2dec "${supported_groups:i:4}")
          # If the Supported groups extensions lists more than one group,
          # then don't include the larger key shares in the extension.
          [[ $i -gt 12 ]] && [[ $group -gt 256 ]] && continue

          # Versions of OpenSSL prior to 1.1.0 cannot perform operations
          # with X25519 keys, so don't include the X25519 key share
          # if the server's response needs to be decrypted and an
          # older version of OpenSSL is being used.
          [[ $i -gt 12 ]] && [[ $group -eq 29 ]] && [[ "$2" == all ]] && ! "$HAS_X25519" && continue

          # Versions of OpenSSL prior to 1.1.1 cannot perform operations
          # with X448 keys, so don't include the X448 key share
          # if the server's response needs to be decrypted and an
          # older version of OpenSSL is being used.
          [[ $i -gt 12 ]] && [[ $group -eq 30 ]] && [[ "$2" == all ]] && ! "$HAS_X448" && continue

          # NOTE: The public keys could be extracted from the private keys
          # (TLS13_KEY_SHARES) using $OPENSSL, but only OpenSSL 1.1.0 and newer can
          # extract the public key from an X25519 private key, and only
          # OpenSSL 1.1.1 can extract the public key from an X448 private key.
          key_share="${TLS13_PUBLIC_KEY_SHARES[group]}"
          if [[ ${#key_share} -gt 4 ]]; then
               key_shares+=",$key_share"
               nr_key_shares+=1
               # Don't include more than two keys, so that the extension isn't too large.
               [[ $nr_key_shares -ge 2 ]] && break
          fi
     done
     [[ -z "$key_shares" ]] && tm_out "" && return 0

     len=${#key_shares}/3
     list_len="$(printf "%04x" "$len")"
     len+=2
     extn_len="$(printf "%04x" "$len")"
     tm_out "00,$KEY_SHARE_EXTN_NR,${extn_len:0:2},${extn_len:2:2},${list_len:0:2},${list_len:2:2}$key_shares"
     return 0
}

# ARG1: TLS version low byte (00: SSLv3,  01: TLS 1.0,  02: TLS 1.1,  03: TLS 1.2)
# ARG2: CIPHER_SUITES string (lowercase, and in the format output by code2network())
# ARG3: "all" - process full response (including Certificate and certificate_status handshake messages)
#       "all+" - same as "all", but do not offer any curves with TLSv1.3 that are not supported by
#                $OPENSSL, since response MUST be decrypted.
#       "ephemeralkey" - extract the server's ephemeral key (if any)
# ARG4: (optional) additional request extensions
# ARG5: (optional): "true" if ClientHello should advertise compression methods other than "NULL"
# ARG6: (optional): "false" if prepare_tls_clienthello() should not open a new socket
#
prepare_tls_clienthello() {
     local tls_low_byte="$1" tls_legacy_version="$1"
     local process_full="$3"
     local new_socket=true
     local tls_word_reclayer="03, 01"      # the first TLS version number is the record layer and always 0301
                                           # -- except: SSLv3 and second ClientHello after HelloRetryRequest
     local servername_hexstr len_servername len_servername_hex
     local hexdump_format_str part1 part2
     local all_extensions=""
     local -i i j len_extension len_padding_extension len_all len_session_id
     local len_sni_listlen len_sni_ext len_extension_hex len_padding_extension_hex
     local cipher_suites len_ciph_suites len_ciph_suites_byte len_ciph_suites_word
     local len_client_hello_word len_all_word
     local ecc_cipher_suite_found=false
     local extension_signature_algorithms extension_heartbeat session_id
     local extension_session_ticket extension_next_protocol extension_padding
     local extension_supported_groups="" extension_supported_point_formats=""
     local extensions_key_share="" extn_type supported_groups_c2n="" extn_psk_mode=""
     local extra_extensions extra_extensions_list="" extension_supported_versions=""
     local offer_compression=false compression_methods

     # TLSv1.3 ClientHello messages MUST specify only the NULL compression method.
     [[ "$5" == true ]] && [[ "0x$tls_low_byte" -le "0x03" ]] && offer_compression=true
     [[ "$6" == false ]] && new_socket=false

     cipher_suites="$2"                      # we don't have the leading \x here so string length is two byte less, see next
     len_ciph_suites_byte=${#cipher_suites}
     let "len_ciph_suites_byte += 2"

     # we have additional 2 chars \x in each 2 byte string and 2 byte ciphers, so we need to divide by 4:
     len_ciph_suites=$(printf "%02x\n" $(( len_ciph_suites_byte / 4 )))
     len2twobytes "$len_ciph_suites"
     len_ciph_suites_word="$LEN_STR"
     #[[ $DEBUG -ge 3 ]] && echo $len_ciph_suites_word

     if [[ "$tls_low_byte" != "00" ]]; then
          # Add extensions

          # Check to see if any ECC cipher suites are included in cipher_suites
          # (not needed for TLSv1.3)
          if [[ "0x$tls_low_byte" -le "0x03" ]]; then
               for (( i=0; i<len_ciph_suites_byte; i+=8 )); do
                    j=$i+4
                    part1="0x${cipher_suites:$i:2}"
                    part2="0x${cipher_suites:$j:2}"
                    if [[ "$part1" == 0xc0 ]]; then
                         if [[ "$part2" -ge 0x01 ]] && [[ "$part2" -le 0x19 ]]; then
                              ecc_cipher_suite_found=true && break
                         elif [[ "$part2" -ge 0x23 ]] && [[ "$part2" -le 0x3b ]]; then
                              ecc_cipher_suite_found=true && break
                         elif [[ "$part2" -ge 0x48 ]] && [[ "$part2" -le 0x4f ]]; then
                              ecc_cipher_suite_found=true && break
                         elif [[ "$part2" -ge 0x5c ]] && [[ "$part2" -le 0x63 ]]; then
                              ecc_cipher_suite_found=true && break
                         elif [[ "$part2" -ge 0x70 ]] && [[ "$part2" -le 0x79 ]]; then
                              ecc_cipher_suite_found=true && break
                         elif [[ "$part2" -ge 0x86 ]] && [[ "$part2" -le 0x8d ]]; then
                              ecc_cipher_suite_found=true && break
                         elif [[ "$part2" -ge 0x9a ]] && [[ "$part2" -le 0x9b ]]; then
                              ecc_cipher_suite_found=true && break
                         elif [[ "$part2" -ge 0xac ]] && [[ "$part2" -le 0xaf ]]; then
                              ecc_cipher_suite_found=true && break
                         fi
                    elif [[ "$part1" == 0xcc ]]; then
                         if [[ "$part2" == 0xa8 ]] || [[ "$part2" == 0xa9 ]] || \
                            [[ "$part2" == 0xac ]] || [[ "$part2" == 0x13 ]] || \
                            [[ "$part2" == 0x14 ]]; then
                              ecc_cipher_suite_found=true && break
                         fi
                    fi
               done
          fi

          if [[ -n "$SNI" ]]; then
               #formatted example for SNI
               #00 00    # extension server_name
               #00 1a    # length                      = the following +2 = server_name length + 5
               #00 18    # server_name list_length     = server_name length +3
               #00       # server_name type (hostname)
               #00 15    # server_name length
               #66 66 66 66 66 66 2e 66 66 66 66 66 66 66 66 66 66 2e 66 66 66  target.mydomain1.tld # server_name target
               len_servername=${#NODE}
               hexdump_format_str="$len_servername/1 \"%02x,\""
               servername_hexstr=$(printf $NODE | hexdump -v -e "${hexdump_format_str}" | sed 's/,$//')
               # convert lengths we need to fill in from dec to hex:
               len_servername_hex=$(printf "%02x\n" $len_servername)
               len_sni_listlen=$(printf "%02x\n" $((len_servername+3)))
               len_sni_ext=$(printf "%02x\n" $((len_servername+5)))
          fi

          if [[ 0x$tls_low_byte -le 0x03 ]]; then
               extension_signature_algorithms="
               00, 0d,                    # Type: signature_algorithms , see RFC 5246
               00, 20, 00,1e,             # lengths
               06,01, 06,02, 06,03, 05,01, 05,02, 05,03, 04,01, 04,02, 04,03,
               03,01, 03,02, 03,03, 02,01, 02,02, 02,03"
          else
               extension_signature_algorithms="
               00, 0d,                    # Type: signature_algorithms , see RFC 8446
               00, 22, 00, 20,            # lengths
               04,03, 05,03, 06,03, 08,04, 08,05, 08,06,
               04,01, 05,01, 06,01, 08,09, 08,0a, 08,0b,
               08,07, 08,08, 02,01, 02,03"
          fi

          extension_heartbeat="
          00, 0f, 00, 01, 01"

          extension_session_ticket="
          00, 23, 00, 00"

          extension_next_protocol="
          33, 74, 00, 00"

          extn_psk_mode="
          00, 2d, 00, 02, 01, 01"

          if "$ecc_cipher_suite_found"; then
               # Supported Groups Extension
               extension_supported_groups="
               00, 0a,                    # Type: Supported Elliptic Curves , see RFC 4492
               00, 3e, 00, 3c,            # lengths
               00, 0e, 00, 0d, 00, 19, 00, 1c, 00, 1e, 00, 0b, 00, 0c, 00, 1b,
               00, 18, 00, 09, 00, 0a, 00, 1a, 00, 16, 00, 17, 00, 1d, 00, 08,
               00, 06, 00, 07, 00, 14, 00, 15, 00, 04, 00, 05, 00, 12, 00, 13,
               00, 01, 00, 02, 00, 03, 00, 0f, 00, 10, 00, 11"
          elif [[ 0x$tls_low_byte -gt 0x03 ]]; then
               # Supported Groups Extension
               if [[ ! "$process_full" =~ all ]] || ( "$HAS_X25519" && "$HAS_X448" ); then
                    extension_supported_groups="
                    00,0a,                      # Type: Supported Groups, see RFC 8446
                    00,10, 00,0e,               # lengths
                    00,1d, 00,17, 00,1e, 00,18, 00,19,
                    01,00, 01,01"
                    # OpenSSL prior to 1.1.1 does not support X448, so list it as the least
                    # preferred option if the response needs to be decrypted, and do not
                    # list it at all if the response MUST be decrypted.
               elif "$HAS_X25519" && [[ "$process_full" == all+ ]]; then
                    extension_supported_groups="
                    00,0a,                      # Type: Supported Groups, see RFC 8446
                    00,0e, 00,0c,               # lengths
                    00,1d, 00,17, 00,18, 00,19,
                    01,00, 01,01"
               elif "$HAS_X25519"; then
                    extension_supported_groups="
                    00,0a,                      # Type: Supported Groups, see RFC 8446
                    00,10, 00,0e,               # lengths
                    00,1d, 00,17, 00,18, 00,19,
                    01,00, 01,01, 00,1e"
                    # OpenSSL prior to 1.1.0 does not support either X25519 or X448,
                    # so list them as the least referred options if the response
                    # needs to be decrypted, and do not list them at all if the
                    # response MUST be decrypted.
               elif [[ "$process_full" == all+ ]]; then
                    extension_supported_groups="
                    00,0a,                      # Type: Supported Groups, see RFC 8446
                    00,0c, 00,0a,               # lengths
                    00,17, 00,18, 00,19,
                    01,00, 01,01"
               else
                    extension_supported_groups="
                    00,0a,                      # Type: Supported Groups, see RFC 8446
                    00,10, 00,0e,               # lengths
                    00,17, 00,18, 00,19,
                    01,00, 01,01, 00,1d, 00,1e"
               fi

               code2network "$extension_supported_groups"
               supported_groups_c2n="$NW_STR"
          fi

          if "$ecc_cipher_suite_found" || [[ 0x$tls_low_byte -gt 0x03 ]]; then
               # Supported Point Formats Extension.
               extension_supported_point_formats="
               00, 0b,                    # Type: Supported Point Formats , see RFC 4492
               00, 02,                    # len
               01, 00"
          fi

          # Each extension should appear in the ClientHello at most once. So,
          # find out what extensions were provided as an argument and only use
          # the provided values for those extensions.
          extra_extensions="$(tolower "$4")"
          code2network "$extra_extensions"
          len_all=${#NW_STR}
          for (( i=0; i < len_all; i+=16+4*0x$len_extension_hex )); do
               part2=$i+4
               extn_type="${NW_STR:i:2}${NW_STR:part2:2}"
               extra_extensions_list+=" $extn_type "
               j=$i+8
               part2=$j+4
               len_extension_hex="${NW_STR:j:2}${NW_STR:part2:2}"
               if [[ "$extn_type" == "000a" ]] && [[ 0x$tls_low_byte -gt 0x03 ]]; then
                    j=14+4*0x$len_extension_hex
                    supported_groups_c2n="${NW_STR:i:j}"
               fi
          done
          if [[ 0x$tls_low_byte -gt 0x03 ]]; then
               extensions_key_share="$(generate_key_share_extension "$supported_groups_c2n" "$process_full")"
               [[ $? -ne 0 ]] && return 1
          fi

          if [[ -n "$SNI" ]] && [[ ! "$extra_extensions_list" =~ " 0000 " ]]; then
               all_extensions="
                00, 00                  # extension server_name
               ,00, $len_sni_ext        # length SNI EXT
               ,00, $len_sni_listlen    # server_name list_length
               ,00                      # server_name type (hostname)
               ,00, $len_servername_hex # server_name length. We assume len(hostname) < FF - 9
               ,$servername_hexstr"     # server_name target
          fi
          if [[ 0x$tls_low_byte -ge 0x04 ]] && [[ ! "$extra_extensions_list" =~ " 002b " ]]; then
               # Add supported_versions extension listing all TLS/SSL versions
               # from the one specified in $tls_low_byte to SSLv3.
               for (( i=0x$tls_low_byte; i >=0; i=i-1 )); do
                    if [[ 0x$i -eq 4 ]]; then
                         # FIXME: The ClientHello currently advertises support for various
                         # draft versions of TLSv1.3. Eventually it should only adversize
                         # support for the final version (0304).
                         if [[ "$KEY_SHARE_EXTN_NR" == 33 ]]; then
                              extension_supported_versions+=", 03, 04, 7f, 1c, 7f, 1b, 7f, 1a, 7f, 19, 7f, 18, 7f, 17"
                         else
                              extension_supported_versions+=", 7f, 16, 7f, 15, 7f, 14, 7f, 13, 7f, 12"
                         fi
                    else
                         extension_supported_versions+=", 03, $(printf "%02x" $i)"
                    fi
               done
               [[ -n "$all_extensions" ]] && all_extensions+=","
               # FIXME: Adjust the lengths ("+15" and "+14") when the draft versions of TLSv1.3 are removed.
               if [[ "$KEY_SHARE_EXTN_NR" == "33" ]]; then
                    all_extensions+="00, 2b, 00, $(printf "%02x" $((2*0x$tls_low_byte+15))), $(printf "%02x" $((2*0x$tls_low_byte+14)))$extension_supported_versions"
               else
                    all_extensions+="00, 2b, 00, $(printf "%02x" $((2*0x$tls_low_byte+11))), $(printf "%02x" $((2*0x$tls_low_byte+10)))$extension_supported_versions"
               fi
          fi

          # There does not seem to be any reason to include this extension. However, it appears that
          # OpenSSL, Firefox, and Chrome include it in TLS 1.3 ClientHello messages, and there is at
          # least one server that will fail the connection if it is absent
          # (see https://github.com/drwetter/testssl.sh/issues/990).
          if [[ "0x$tls_low_byte" -ge 0x04 ]] && [[ ! "$extra_extensions_list" =~ " 002d " ]]; then
               [[ -n "$all_extensions" ]] && all_extensions+=","
               all_extensions+="$extn_psk_mode"
          fi

          if [[ ! "$extra_extensions_list" =~ " 0023 " ]]; then
               [[ -n "$all_extensions" ]] && all_extensions+=","
               all_extensions+="$extension_session_ticket"
          fi

          # If the ClientHello will include the ALPN extension, then don't include the NPN extension.
          if [[ ! "$extra_extensions_list" =~ " 3374 " ]] && [[ ! "$extra_extensions_list" =~ " 0010 " ]]; then
               [[ -n "$all_extensions" ]] && all_extensions+=","
               all_extensions+="$extension_next_protocol"
          fi

          # RFC 5246 says that clients MUST NOT offer the signature algorithms
          # extension if they are offering TLS versions prior to 1.2.
          if [[ "0x$tls_low_byte" -ge 0x03 ]] && [[ ! "$extra_extensions_list" =~ " 000d " ]]; then
               [[ -n "$all_extensions" ]] && all_extensions+=","
               all_extensions+="$extension_signature_algorithms"
          fi

          if [[ -n "$extension_supported_groups" ]] && [[ ! "$extra_extensions_list" =~ " 000a " ]]; then
               [[ -n "$all_extensions" ]] && all_extensions+=","
               all_extensions+="$extension_supported_groups"
          fi

          if [[ -n "$extensions_key_share" ]] && [[ ! "$extra_extensions_list" =~ " 00$KEY_SHARE_EXTN_NR " ]]; then
               [[ -n "$all_extensions" ]] && all_extensions+=","
               all_extensions+="$extensions_key_share"
          fi

          if [[ -n "$extension_supported_point_formats" ]] && [[ ! "$extra_extensions_list" =~ " 000b " ]]; then
               [[ -n "$all_extensions" ]] && all_extensions+=","
               all_extensions+="$extension_supported_point_formats"
          fi

          if [[ -n "$extra_extensions" ]]; then
               [[ -n "$all_extensions" ]] && all_extensions+=","
               all_extensions+="$extra_extensions"
          fi

          # Make sure that a non-empty extension goes last (either heartbeat or padding).
          # See PR #792 and https://www.ietf.org/mail-archive/web/tls/current/msg19720.html.
          if [[ ! "$extra_extensions_list" =~ " 000f " ]]; then
               [[ -n "$all_extensions" ]] && all_extensions+=","
               all_extensions+="$extension_heartbeat"
          fi

          code2network "$all_extensions" # convert extensions
          all_extensions="$NW_STR"       # we don't have the leading \x here so string length is two byte less, see next
          len_extension=${#all_extensions}
          len_extension+=2
          len_extension=$len_extension/4
          len_extension_hex=$(printf "%02x\n" $len_extension)

          # If the length of the Client Hello would be between 256 and 511 bytes,
          # then add a padding extension (see RFC 7685)
          len_all=$((0x$len_ciph_suites + 0x2b + 0x$len_extension_hex + 0x2))
          "$offer_compression" && len_all+=2
          [[ 0x$tls_low_byte -gt 0x03 ]] && len_all+=32 # TLSv1.3 ClientHello includes a 32-byte session id
          if [[ $len_all -ge 256 ]] && [[ $len_all -le 511 ]] && [[ ! "$extra_extensions_list" =~ " 0015 " ]]; then
               if [[ $len_all -ge 508 ]]; then
                    len_padding_extension=1 # Final extension cannot be empty: see PR #792
               else
                    len_padding_extension=$((508 - len_all))
               fi
               len_padding_extension_hex=$(printf "%02x\n" $len_padding_extension)
               len2twobytes "$len_padding_extension_hex"
               all_extensions="$all_extensions\\x00\\x15\\x${LEN_STR:0:2}\\x${LEN_STR:4:2}"
               for (( i=0; i<len_padding_extension; i++ )); do
                    all_extensions="$all_extensions\\x00"
               done
               len_extension=$len_extension+$len_padding_extension+0x4
               len_extension_hex=$(printf "%02x\n" $len_extension)
          elif [[ ! "$extra_extensions_list" =~ " 0015 " ]] && ( [[ $((len_all%256)) -eq 10 ]] || [[ $((len_all%256)) -eq 14 ]] ); then
               # Some servers fail if the length of the ClientHello is 522, 778, 1034, 1290, ... bytes.
               # A few servers also fail if the length is 526, 782, 1038, 1294, ... bytes.
               # So, if the ClientHello would be one of these length, add a 5-byte padding extension.
               all_extensions="$all_extensions\\x00\\x15\\x00\\x01\\x00"
               len_extension+=5
               len_extension_hex=$(printf "%02x\n" $len_extension)
          fi
          len2twobytes "$len_extension_hex"
          all_extensions="
          ,$LEN_STR  # first the len of all extensions.
          ,$all_extensions"
     fi

     if [[ 0x$tls_low_byte -gt 0x03 ]]; then
          # TLSv1.3 calls for sending a random 32-byte session id in middlebox compatibility mode.
          session_id="20,44,b8,92,56,af,74,52,9e,d8,cf,52,14,c8,af,d8,34,0b,e7,7f,eb,86,01,84,50,5d,e4,a1,6a,09,3b,bf,6e"
          len_session_id=32
     else
          session_id="00"
          len_session_id=0
     fi

     # RFC 3546 doesn't specify SSLv3 to have SNI, openssl just ignores the switch if supplied
     if [[ "$tls_low_byte" == "00" ]]; then
          len_all=$((0x$len_ciph_suites + len_session_id + 0x27))
     else
          len_all=$((0x$len_ciph_suites + len_session_id + 0x27 + 0x$len_extension_hex + 0x2))
     fi
     "$offer_compression" && len_all+=2
     len2twobytes $(printf "%02x\n" $len_all)
     len_client_hello_word="$LEN_STR"
     #[[ $DEBUG -ge 3 ]] && echo $len_client_hello_word

     if [[ "$tls_low_byte" == 00 ]]; then
          len_all=$((0x$len_ciph_suites + len_session_id + 0x2b))
     else
          len_all=$((0x$len_ciph_suites + len_session_id + 0x2b + 0x$len_extension_hex + 0x2))
     fi
     "$offer_compression" && len_all+=2
     len2twobytes $(printf "%02x\n" $len_all)
     len_all_word="$LEN_STR"
     #[[ $DEBUG -ge 3 ]] && echo $len_all_word

     # if we have SSLv3, the first occurrence of TLS protocol -- record layer -- is SSLv3, otherwise TLS 1.0,
     # except in the case of a second ClientHello in TLS 1.3, in which case it is TLS 1.2.
     [[ $tls_low_byte == "00" ]] && tls_word_reclayer="03, 00"

     [[ 0x$tls_legacy_version -ge 0x04 ]] && tls_legacy_version="03"

     if "$offer_compression"; then
          # See https://www.iana.org/assignments/comp-meth-ids/comp-meth-ids.xhtml#comp-meth-ids-2
          compression_methods="03,01,40,00" # Offer NULL, DEFLATE, and LZS compression
     else
          compression_methods="01,00" # Only offer NULL compression (0x00)
     fi

     TLS_CLIENT_HELLO="
     # TLS header ( 5 bytes)
     ,16, $tls_word_reclayer  # TLS Version: in wireshark this is always 01 for TLS 1.0-1.2
     ,$len_all_word           # Length  <---
     # Handshake header:
     ,01                      # Type (x01 for ClientHello)
     ,00, $len_client_hello_word   # Length ClientHello
     ,03, $tls_legacy_version # TLS version ClientHello
     ,54, 51, 1e, 7a          # Unix time since  see www.moserware.com/2009/06/first-few-milliseconds-of-https.html
     ,de, ad, be, ef          # Random 28 bytes
     ,31, 33, 07, 00, 00, 00, 00, 00
     ,cf, bd, 39, 04, cc, 16, 0b, 85
     ,03, 90, 9f, 77, 04, 33, d4, de
     ,$session_id
     ,$len_ciph_suites_word   # Cipher suites length
     ,$cipher_suites
     ,$compression_methods"

     if "$new_socket"; then
          fd_socket 5 || return 6
     fi

     debugme echo -n "sending client hello... "
     socksend_clienthello "$TLS_CLIENT_HELLO$all_extensions" $USLEEP_SND

     if [[ "$tls_low_byte" -gt 0x03 ]]; then
          TLS_CLIENT_HELLO="$(tolower "$NW_STR")"
          TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x0\\/\\x00\\}"
          TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x1\\/\\x01\\}"
          TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x2\\/\\x02\\}"
          TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x3\\/\\x03\\}"
          TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x4\\/\\x04\\}"
          TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x5\\/\\x05\\}"
          TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x6\\/\\x06\\}"
          TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x7\\/\\x07\\}"
          TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x8\\/\\x08\\}"
          TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x9\\/\\x09\\}"
          TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\xa\\/\\x0a\\}"
          TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\xb\\/\\x0b\\}"
          TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\xc\\/\\x0c\\}"
          TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\xd\\/\\x0d\\}"
          TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\xe\\/\\x0e\\}"
          TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\xf\\/\\x0f\\}"
          TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO//\\x/}"
          TLS_CLIENT_HELLO="${TLS_CLIENT_HELLO:10}"
     fi

     return 0
}

# arg1: The original ClientHello
# arg2: The server's response
# Return 0 if the response is not a HelloRetryRequest.
# Return 1 if the response is a malformed HelloRetryRequest or if a new ClientHello cannot be sent.
# Return 2 if the response is a HelloRetryRequest, and sending a new ClientHello succeeded.
# Return 6 if the response is a HelloRetryRequest, and sending a new ClientHello failed.
resend_if_hello_retry_request() {
     local original_clienthello="$1"
     local tls_hello_ascii="$2"
     local msg_type tls_low_byte server_version cipher_suite rfc_cipher_suite
     local key_share="" new_key_share="" cookie="" second_clienthello data=""
     local -i i j msg_len tls_hello_ascii_len sid_len
     local -i extns_offset hrr_extns_len extra_extensions_len len_extn
     local extra_extensions extn_type part2 new_extra_extns=""
     local sha256_hrr="CF21AD74E59A6111BE1D8C021E65B891C2A211167ABB8C5E079E09E2C8A8339C"

     tls_hello_ascii_len=${#tls_hello_ascii}
     # A HelloRetryRequest is at least 13 bytes long
     [[ $tls_hello_ascii_len -lt 26 ]] && return 0
     # A HelloRetryRequest is a handshake message (16) with a major record version of 03.
     [[ "${tls_hello_ascii:0:4}" != 1603 ]] && return 0
     msg_type="${tls_hello_ascii:10:2}"
     if [[ "$msg_type" == 02 ]]; then
          # A HRR is a ServerHello with a Random value equal to the
          # SHA-256 hash of "HelloRetryRequest"
          [[ $tls_hello_ascii_len -lt 76 ]] && return 0
          [[ "${tls_hello_ascii:22:64}" != $sha256_hrr ]] && return 0
     elif [[ "$msg_type" != 06 ]]; then
          # The handshake type for hello_retry_request in draft versions was 06.
          return 0
     fi

     # This appears to be a HelloRetryRequest message.
     debugme echo "reading hello retry request... "
     if [[ "$DEBUG" -ge 4 ]]; then
          hexdump -C $SOCK_REPLY_FILE | head -6
          echo
          [[ "$DEBUG" -ge 5 ]] && echo "$tls_hello_ascii"      # one line without any blanks
     fi

     # Check the length of the handshake message
     msg_len=2*$(hex2dec "${tls_hello_ascii:6:4}")
     if [[ $msg_len -gt $tls_hello_ascii_len-10 ]]; then
          debugme echo "malformed HelloRetryRequest"
          return 1
     fi
     # The HelloRetryRequest message may be followed by something
     # else (e.g., a change cipher spec message). Ignore anything
     # that follows.
     tls_hello_ascii_len=$msg_len+10

     # Check the length of the HelloRetryRequest message.
     msg_len=2*$(hex2dec "${tls_hello_ascii:12:6}")
     if [[ $msg_len -ne $tls_hello_ascii_len-18 ]]; then
          debugme echo "malformed HelloRetryRequest"
          return 1
     fi

     if [[ "$msg_type" == 06 ]]; then
          server_version="${tls_hello_ascii:18:4}"
          if [[ 0x$server_version -ge 0x7f13 ]]; then
               # Starting with TLSv1.3 draft 19, a HelloRetryRequest is at least 15 bytes long
               [[ $tls_hello_ascii_len -lt 30 ]] && return 0
               cipher_suite="${tls_hello_ascii:22:2},${tls_hello_ascii:24:2}"
               extns_offset=26
          else
               extns_offset=22
          fi
     else
          sid_len=2*$(hex2dec "${tls_hello_ascii:86:2}")
          i=88+$sid_len
          j=90+$sid_len
          cipher_suite="${tls_hello_ascii:i:2},${tls_hello_ascii:j:2}"
          extns_offset=94+$sid_len
     fi

     # Check the length of the extensions.
     hrr_extns_len=2*$(hex2dec "${tls_hello_ascii:extns_offset:4}")
     if [[ $hrr_extns_len -ne $tls_hello_ascii_len-$extns_offset-4 ]]; then
          debugme echo "malformed HelloRetryRequest"
          return 1
     fi

     # Parse HelloRetryRequest extensions
     for (( i=extns_offset+4; i < tls_hello_ascii_len; i+=8+len_extn )); do
          extn_type="${tls_hello_ascii:i:4}"
          j=$i+4
          len_extn=2*$(hex2dec "${tls_hello_ascii:j:4}")
          j+=4
          if [[ $len_extn -gt $tls_hello_ascii_len-$j ]]; then
               debugme echo "malformed HelloRetryRequest"
               return 1
          fi
          if [[ "$extn_type" == 002C ]]; then
               # If the HRR includes a cookie extension, then it needs to be
               # included in the next ClientHello.
               j=8+$len_extn
               cookie="${tls_hello_ascii:i:j}"
          elif [[ "$extn_type" == 00$KEY_SHARE_EXTN_NR ]]; then
               # If the HRR includes a key_share extension, then it specifies the
               # group to be used in the next ClientHello. So, create a key_share
               # extension that specifies this group.
               if [[ $len_extn -ne 4 ]]; then
                    debugme echo "malformed key share extension in HelloRetryRequest"
                    return 1
               fi
               key_share="${tls_hello_ascii:j:4}"
               new_key_share="$(generate_key_share_extension "000a00040002$key_share" "ephemeralkey")"
               [[ $? -ne 0 ]] && return 1
               [[ -z "$new_key_share" ]] && return 1
               new_key_share="${new_key_share//,/}"
          elif [[ "$extn_type" == 002B ]]; then
               if [[ $len_extn -ne 4 ]]; then
                    debugme echo "malformed supported versions extension in HelloRetryRequest"
                    return 1
               fi
               server_version="${tls_hello_ascii:j:4}"
          fi
     done

     if [[ $DEBUG -ge 3 ]]; then
          echo "TLS message fragments:"
          echo "     tls_protocol (reclyr):  0x${tls_hello_ascii:2:4}"
          echo "     tls_content_type:       0x16 (handshake)"
          echo "     msg_len:                $(hex2dec "${tls_hello_ascii:6:4}")"
          echo
          echo "TLS handshake message:"
          echo -n "     handshake type:         0x$msg_type "
          case "$msg_type" in
               02) echo "(hello_retry_request formatted as server_hello)" ;;
               06) echo "(hello_retry_request)" ;;
          esac
          echo "     msg_len:                $(hex2dec "${tls_hello_ascii:12:6}")"
          echo
          echo "TLS hello retry request message:"
          echo "     server version:         $server_version"
          if [[ "$server_version" == 0304 ]] || [[ 0x$server_version -ge 0x7f13 ]]; then
               echo -n "     cipher suite:           $cipher_suite"
               if [[ $TLS_NR_CIPHERS -ne 0 ]]; then
                    if [[ "${cipher_suite:0:2}" == "00" ]]; then
                         rfc_cipher_suite="$(show_rfc_style "x${cipher_suite:3:2}")"
                    else
                         rfc_cipher_suite="$(show_rfc_style "x${cipher_suite:0:2}${cipher_suite:3:2}")"
                    fi
               elif "$HAS_CIPHERSUITES"; then
                    rfc_cipher_suite="$($OPENSSL ciphers -V -ciphersuites "$TLS13_OSSL_CIPHERS" 'ALL:COMPLEMENTOFALL' | grep -i " 0x${cipher_suite:0:2},0x${cipher_suite:3:2} " | awk '{ print $3 }')"
               else
                    rfc_cipher_suite="$($OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL' | grep -i " 0x${cipher_suite:0:2},0x${cipher_suite:3:2} " | awk '{ print $3 }')"
               fi
               if [[ -n "$rfc_cipher_suite" ]]; then
                    echo " ($rfc_cipher_suite)"
               else
                    echo ""
               fi
          fi
          [[ -n "$key_share" ]] && echo "     key share:              0x$key_share"
          [[ -n "$cookie" ]] && echo "     cookie:                 $cookie"
     fi

     # Starting with TLSv1.3 draft 24, the second ClientHello should specify a record layer version of 0x0303
     if [[ "$server_version" == 0304 ]] || [[ 0x$server_version -ge 0x7f18 ]]; then
          original_clienthello="160303${original_clienthello:6}"
     fi

     if [[ "$server_version" == 0304 ]] || [[ 0x$server_version -ge 0x7f16 ]]; then
          # Send a dummy change cipher spec for middlebox compatibility.
          debugme echo -en "\nsending dummy change cipher spec... "
          socksend ", x14, x03, x03 ,x00, x01, x01" 0
     fi
     debugme echo -en "\nsending second client hello... "
     second_clienthello="$(modify_clienthello "$original_clienthello" "$new_key_share" "$cookie")"
     TLS_CLIENT_HELLO="${second_clienthello:10}"
     msg_len=${#second_clienthello}
     for (( i=0; i < msg_len; i+=2 )); do
          data+=", ${second_clienthello:i:2}"
     done
     debugme echo -n "sending client hello... "
     socksend_clienthello "$data" $USLEEP_SND
     sockread_serverhello 32768
     return 2
}

# arg1: TLS version low byte
#       (00: SSLv3,  01: TLS 1.0,  02: TLS 1.1,  03: TLS 1.2)
# arg2: (optional) list of cipher suites
# arg3: (optional): "all" - process full response (including Certificate and certificate_status handshake messages)
#                   "all+" - same as "all", but do not offer any curves with TLSv1.3 that are not supported by
#                            $OPENSSL, since response MUST be decrypted.
#                   "ephemeralkey" - extract the server's ephemeral key (if any)
# arg4: (optional) additional request extensions
# arg5: (optional) "true" if ClientHello should advertise compression methods other than "NULL"
# arg6: (optional) "false" if the connection should not be closed before the function returns.
# return: 0: successful connect   | 1: protocol or cipher not available | 2: as (0) but downgraded
#         6: couldn't open socket | 7: couldn't open temp file
tls_sockets() {
     local -i ret=0
     local -i save=0
     local lines
     local tls_low_byte
     local cipher_list_2send
     local sock_reply_file2 sock_reply_file3
     local tls_hello_ascii next_packet
     local clienthello1 original_clienthello hrr=""
     local process_full="$3" offer_compression=false skip=false
     local close_connection=true include_headers=true
     local -i i len tag_len hello_done=0
     local cipher="" tls_version handshake_secret="" res
     local initial_msg_transcript msg_transcript finished_msg aad="" data=""
     local handshake_traffic_keys key iv finished_key
     local master_secret master_traffic_keys

     [[ "$5" == true ]] && offer_compression=true
     [[ "$6" == false ]] && close_connection=false
     tls_low_byte="$1"
     if [[ -n "$2" ]]; then             # use supplied string in arg2 if there is one
          cipher_list_2send="$2"
     else                               # otherwise use std ciphers then
          if [[ "$tls_low_byte" == 03 ]]; then
               cipher_list_2send="$TLS12_CIPHER"
          else
               cipher_list_2send="$TLS_CIPHER"
          fi
     fi
     code2network "$(tolower "$cipher_list_2send")"   # convert CIPHER_SUITES to a "standardized" format
     cipher_list_2send="$NW_STR"

     debugme echo -en "\nsending client hello... "
     prepare_tls_clienthello "$tls_low_byte" "$cipher_list_2send" "$process_full" "$4" "$offer_compression"
     ret=$?                             # 6 means opening socket didn't succeed, e.g. timeout

     # if sending didn't succeed we don't bother
     if [[ $ret -eq 0 ]]; then
          clienthello1="$TLS_CLIENT_HELLO"
          sockread_serverhello 32768
          "$TLS_DIFFTIME_SET" && TLS_NOW=$(LC_ALL=C date "+%s")

          tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE")
          tls_hello_ascii="${tls_hello_ascii%%[!0-9A-F]*}"
          tls_hello_ascii="${tls_hello_ascii%%140303000101}"

          # Check if the response is a HelloRetryRequest.
          original_clienthello="160301$(printf "%04x" "${#clienthello1}")$clienthello1"
          resend_if_hello_retry_request "$original_clienthello" "$tls_hello_ascii"
          ret=$?
          if [[ $ret -eq 2 ]]; then
               hrr="${tls_hello_ascii:10}"
               tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE")
               tls_hello_ascii="${tls_hello_ascii%%[!0-9A-F]*}"
          elif [[ $ret -eq 1 ]] || [[ $ret -eq 6 ]]; then
               close_socket
               TMPFILE=$SOCK_REPLY_FILE
               tmpfile_handle ${FUNCNAME[0]}.dd
               return $ret
          fi

          # The server's response may span more than one packet. If only the
          # first part of the response needs to be processed, this isn't an
          # issue. However, if the entire response needs to be processed or
          # if the ephemeral key is needed (which comes last for TLS 1.2 and
          # below), then we need to check if response appears to be complete,
          # and if it isn't then try to get another packet from the server.
          if [[ "$process_full" =~ all ]] || [[ "$process_full" == ephemeralkey ]]; then
               hello_done=1; skip=true
          fi
          for (( 1 ; hello_done==1; 1 )); do
               if ! "$skip"; then
                    if [[ $DEBUG -ge 1 ]]; then
                         sock_reply_file2=$(mktemp $TEMPDIR/ddreply.XXXXXX) || return 7
                         mv "$SOCK_REPLY_FILE" "$sock_reply_file2"
                    fi

                    debugme echo -n "requesting more server hello data... "
                    socksend "" $USLEEP_SND
                    sockread_serverhello 32768

                    next_packet=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE")
                    next_packet="${next_packet%%[!0-9A-F]*}"

                    if [[ ${#next_packet} -eq 0 ]]; then
                         # This shouldn't be necessary. However, it protects against
                         # getting into an infinite loop if the server has nothing
                         # left to send and check_tls_serverhellodone doesn't
                         # correctly catch it.
                         [[ $DEBUG -ge 1 ]] && mv "$sock_reply_file2" "$SOCK_REPLY_FILE"
                         hello_done=0
                    else
                         tls_hello_ascii+="$next_packet"
                         if [[ $DEBUG -ge 1 ]]; then
                              sock_reply_file3=$(mktemp $TEMPDIR/ddreply.XXXXXX) || return 7
                              mv "$SOCK_REPLY_FILE" "$sock_reply_file3"
                              mv "$sock_reply_file2" "$SOCK_REPLY_FILE"
                              cat "$sock_reply_file3" >> "$SOCK_REPLY_FILE"
                              rm "$sock_reply_file3"
                         fi
                    fi
               fi
               skip=false
               if [[ $hello_done -eq 1 ]]; then
                    res="$(check_tls_serverhellodone "$tls_hello_ascii" "$process_full" "$cipher" "$handshake_secret" "$initial_msg_transcript")"
                    hello_done=$?
                    if [[ "$hello_done" -eq 0 ]] && [[ -n "$res" ]]; then
                         read -r msg_transcript tls_hello_ascii <<< "$res"
                         tls_hello_ascii="$(toupper "$tls_hello_ascii")"
                    fi
                    if [[ "$hello_done" -eq 3 ]]; then
                         hello_done=1; skip=true
                         debugme echo "reading server hello..."
                         parse_tls_serverhello "$tls_hello_ascii" "ephemeralkey"
                         ret=$?
                         if [[ "$ret" -eq 0 ]] || [[ "$ret" -eq 2 ]]; then
                              cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")
                              if [[ -n "$hrr" ]]; then
                                   initial_msg_transcript="$(create-initial-transcript "$cipher" "$clienthello1" "$hrr" "$TLS_CLIENT_HELLO" "$TLS_SERVER_HELLO")"
                              else
                                   initial_msg_transcript="$(create-initial-transcript "$cipher" "" "" "$TLS_CLIENT_HELLO" "$TLS_SERVER_HELLO")"
                              fi
                              handshake_secret="$(derive-handshake-secret "$cipher" "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")"
                              [[ $? -ne 0 ]] && hello_done=2
                         else
                              hello_done=2
                         fi
                    fi
               fi
          done

          debugme echo "reading server hello..."
          if [[ "$DEBUG" -ge 4 ]]; then
               hexdump -C $SOCK_REPLY_FILE | head -6
               echo
          fi

          parse_tls_serverhello "$tls_hello_ascii" "$process_full" "$cipher_list_2send"
          save=$?
          if "$close_connection" && [[ $save == 0 ]]; then
               send_close_notify "$DETECTED_TLS_VERSION"
          fi

          if [[ $DEBUG -ge 2 ]]; then
               # see https://secure.wand.net.nz/trac/libprotoident/wiki/SSL
               lines=$(count_lines "$(hexdump -C "$SOCK_REPLY_FILE" 2>$ERRFILE)")
               tm_out "  ($lines lines returned)  "
          fi

          if ! "$close_connection" && [[ $save == 0 ]] && \
             [[ -n "$handshake_secret" ]] && [[ "$process_full" == all+ ]]; then
               tls_version="$DETECTED_TLS_VERSION"
               if [[ "${TLS_SERVER_HELLO:8:3}" == 7F1 ]]; then
                    tls_version="${TLS_SERVER_HELLO:8:4}"
               elif [[ "$TLS_SERVER_HELLO" =~ 002B00027F1[0-9A-F] ]]; then
                    tls_version="${BASH_REMATCH:8:4}"
               fi
               [[ "${tls_version:0:2}" == 7F ]] && [[ 0x${tls_version:2:2} -lt 25 ]] && include_headers=false

               handshake_traffic_keys="$(derive-handshake-traffic-keys "$cipher" "$handshake_secret" "$initial_msg_transcript" "client")"
               read -r key iv finished_key <<< "$handshake_traffic_keys"
               if [[ "$cipher" == *SHA256 ]]; then
                    finished_msg="14000020$(hmac-transcript "-sha256" "$finished_key" "$msg_transcript")"
               else
                    finished_msg="14000030$(hmac-transcript "-sha384" "$finished_key" "$msg_transcript")"
               fi
               [[ "$cipher" =~ CCM_8 ]] && tag_len=8 || tag_len=16
               aad="170303$(printf "%04X" "$(( ${#finished_msg}/2 + tag_len + 1 ))")"
               if "$include_headers"; then
                    # The header information was added to additional data in TLSv1.3 draft 25.
                    finished_msg="$(sym-encrypt "$cipher" "$key" "$(get-nonce "$iv" 0)" "${finished_msg}16" "$aad")"
               else
                    finished_msg="$(sym-encrypt "$cipher" "$key" "$(get-nonce "$iv" 0)" "${finished_msg}16" "")"
               fi
               finished_msg="$aad$finished_msg"

               len=${#finished_msg}
               for (( i=0; i < len; i+=2 )); do
                    data+=", ${finished_msg:i:2}"
               done
               debugme echo -e "\nsending finished..."
               socksend_clienthello "${data}"
               sleep $USLEEP_SND

               # Compute application traffic keys and IVs.
               master_secret="$(derive-master-secret "$cipher" "$handshake_secret")"
               master_traffic_keys="$(derive-application-traffic-keys "$cipher" "$master_secret" "$msg_transcript" server)"
               APP_TRAF_KEY_INFO="$tls_version $cipher $master_traffic_keys 0 "
               master_traffic_keys="$(derive-application-traffic-keys "$cipher" "$master_secret" "$msg_transcript" client)"
               APP_TRAF_KEY_INFO+="$master_traffic_keys 0"

               # Some servers send new session tickets as soon as the handshake is complete.
               [[ -s "$TEMPDIR/$NODEIP.parse_tls13_new_session_ticket.txt" ]] && \
                    rm "$TEMPDIR/$NODEIP.parse_tls13_new_session_ticket.txt"
               receive_app_data
               if [[ $? -eq 0 ]] && [[ $DEBUG -ge 2 ]]; then
                    [[ -s "$TEMPDIR/$NODEIP.parse_tls13_new_session_ticket.txt" ]] && \
                         echo -n "Ticket: " && cat "$TEMPDIR/$NODEIP.parse_tls13_new_session_ticket.txt"
                    [[ -s $TMPFILE ]] && echo -n "Unexpected response: " && cat "$TMPFILE"
               fi
          fi

          # determine the return value for higher level, so that they can tell what the result is
          if [[ $save -eq 1 ]] || [[ $lines -eq 1 ]]; then
               ret=1          # NOT available
          elif [[ $save -eq 3 ]]; then
               # only for IMAP currently 'a002 NO Starttls'
               ret=3
          elif [[ $save -eq 8 ]]; then
               # odd return, we just pass this from parse_tls_serverhello() back
               ret=8
          elif [[ $save -eq 4 ]]; then
               # STARTTLS problem passing back
               ret=4
          else
               if [[ 03$tls_low_byte -eq $DETECTED_TLS_VERSION ]]; then
                    ret=0     # protocol available, TLS version returned equal to the one send
               else
                    debugme echo -n "protocol send: 0x03$tls_low_byte, returned: 0x$DETECTED_TLS_VERSION"
                    ret=2     # protocol NOT available, server downgraded to $DETECTED_TLS_VERSION
               fi
          fi
          debugme echo
     else
          debugme echo "stuck on sending: $ret"
     fi

     "$close_connection" && close_socket
     tmpfile_handle ${FUNCNAME[0]}.dd $SOCK_REPLY_FILE
     return $ret
}

# Send application data over a TLS 1.3 channel that has already been created.
send_app_data() {
     local plaintext="$1"
     local tls_version cipher client_key client_iv server_key server_iv
     local aad res data
     local -i i client_seq server_seq tag_len len
     local include_headers=true

     read -r tls_version cipher server_key server_iv server_seq client_key client_iv client_seq <<< "$APP_TRAF_KEY_INFO"
     [[ "${tls_version:0:2}" == 7F ]] && [[ 0x${tls_version:2:2} -lt 25 ]] && include_headers=false
     [[ "$cipher" =~ CCM_8 ]] && tag_len=8 || tag_len=16

     aad="170303$(printf "%04X" "$(( ${#plaintext}/2 + tag_len + 1 ))")"
     if "$include_headers"; then
          res="$(sym-encrypt "$cipher" "$client_key" "$(get-nonce "$client_iv" $client_seq)" "${plaintext}17" "$aad")"
     else
          res="$(sym-encrypt "$cipher" "$client_key" "$(get-nonce "$client_iv" $client_seq)" "${plaintext}17" "")"
     fi
     [[ $? -eq 0 ]] || return 1
     client_seq+=1
     APP_TRAF_KEY_INFO="$tls_version $cipher $server_key $server_iv $server_seq $client_key $client_iv $client_seq"

     res="$aad$res"
     len=${#res}
     data=""
     for (( i=0; i < len; i+=2 )); do
          data+=",x${res:i:2}"
     done
     socksend "$data" $USLEEP_SND
}

# Receive application data from a TLS 1.3 channel that has already been created.
# arg1: true if only the first block of application data should be decrypted.
#       This can save a lot of time if the server sends a lot a data (e.g., a
#       big home page), but only the first part of the data is needed. However,
#       no further data may be received over this connection as the message
#       sequence number will not be correct.
receive_app_data() {
     local plaintext=""
     local tls_version cipher client_key client_iv server_key server_iv
     local aad ciphertext="" res="" data
     local -i client_seq server_seq len msg_len
     local include_headers=true
     local first_block_only=false

     [[ "$1" == true ]] && first_block_only=true

     read -r tls_version cipher server_key server_iv server_seq client_key client_iv client_seq <<< "$APP_TRAF_KEY_INFO"
     [[ "${tls_version:0:2}" == 7F ]] && [[ 0x${tls_version:2:2} -lt 25 ]] && include_headers=false

     sleep $USLEEP_REC
     while true; do
          len=${#ciphertext}
          if [[ $len -ge 10 ]]; then
               [[ "${ciphertext:0:5}" == 17030 ]] || break
               msg_len=$((2*0x${ciphertext:6:4}))
          fi
          if [[ $len -lt 10 ]] || [[ $len -lt $((msg_len+10)) ]]; then
               if "$FAST_SOCKET"; then
                    res="$(sockread_fast 32768)"
               else
                    sockread_serverhello 32768
                    res="$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE")"
               fi
               res="${res%%[!0-9A-F]*}"
               [[ -z "$res" ]] && break
               ciphertext+="$res"
               continue
          fi
          "$include_headers" && aad="${ciphertext:0:10}" || aad=""
          data="$(sym-decrypt "$cipher" "$server_key" "$(get-nonce "$server_iv" "$server_seq")" "${ciphertext:10:msg_len}" "$aad")"
          [[ $? -eq 0 ]] || return 1

          len=${#data}-2
          while [[ "${data:len:2}" == 00 ]]; do
               len=$((len-2))
          done
          content_type="${data:len:2}"

          if [[ "$content_type" == 16 ]] && [[ "${data:0:2}" == 04 ]]; then
               # This is a new_session_ticket
               parse_tls13_new_session_ticket "$tls_version" "${data:0:len}"
          elif [[ "$content_type" == 17 ]]; then
               # This really is application data.
               plaintext+="${data:0:len}"
               "$first_block_only" && break
          fi
          ciphertext=${ciphertext:$((msg_len+10))}
          server_seq+=1
          [[ -z "$ciphertext" ]] && break
     done
     APP_TRAF_KEY_INFO="$tls_version $cipher $server_key $server_iv $server_seq $client_key $client_iv $client_seq"
     asciihex_to_binary "$plaintext" > "$TMPFILE"
     return 0
}


####### Vulnerabilities follow #######
# General overview which browser "supports" which vulnerability:
# https://en.wikipedia.org/wiki/Transport_Layer_Security#Web_browsers

# mainly adapted from https://gist.github.com/takeshixx/10107280
#
run_heartbleed(){
     local tls_hexcode
     local heartbleed_payload
     local -i n lines_returned
     local append=""
     local tls_hello_ascii=""
     local jsonID="heartbleed"
     local cve="CVE-2014-0160"
     local cwe="CWE-119"
     local hint=""

     [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for heartbleed vulnerability " && outln
     pr_bold " Heartbleed"; out " ($cve)                "

     if ( [[ "$STARTTLS_PROTOCOL" =~ ldap ]] || [[ "$STARTTLS_PROTOCOL" =~ irc ]] ); then
          prln_local_problem "STARTTLS/$STARTTLS_PROTOCOL and --ssl-native collide here"
          return 1
     fi

     [[ -z "$TLS_EXTENSIONS" ]] && determine_tls_extensions
     if [[ ! "${TLS_EXTENSIONS}" =~ heartbeat ]]; then
          pr_svrty_best "not vulnerable (OK)"
          outln ", no heartbeat extension"
          fileout "$jsonID" "OK" "not vulnerable, no heartbeat extension" "$cve" "$cwe"
          return 0
     fi

     if [[ 0 -eq $(has_server_protocol tls1) ]]; then
          tls_hexcode="x03, x01"
     elif [[ 0 -eq $(has_server_protocol tls1_1) ]]; then
          tls_hexcode="x03, x02"
     elif [[ 0 -eq $(has_server_protocol tls1_2) ]]; then
          tls_hexcode="x03, x03"
     elif [[ 0 -eq $(has_server_protocol ssl3) ]]; then
          tls_hexcode="x03, x00"
     else # no protocol for some reason defined, determine TLS versions offered with a new handshake
          $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY") >$TMPFILE 2>$ERRFILE </dev/null
          case "$(get_protocol $TMPFILE)" in
               *1.2)  tls_hexcode="x03, x03" ; add_proto_offered tls1_2 yes ;;
               *1.1)  tls_hexcode="x03, x02" ; add_proto_offered tls1_1 yes ;;
               TLSv1) tls_hexcode="x03, x01" ; add_proto_offered tls1 yes ;;
               SSLv3) tls_hexcode="x03, x00" ; add_proto_offered ssl3 yes ;;
          esac
     fi
     debugme echo "using protocol $tls_hexcode"

     heartbleed_payload=", x18, $tls_hexcode, x00, x03, x01, x40, x00"
     tls_sockets "${tls_hexcode:6:2}" "" "ephemeralkey" "" "" "false"

     [[ $DEBUG -ge 4 ]] && tmln_out "\nsending payload with TLS version $tls_hexcode:"
     socksend "$heartbleed_payload" 1
     sockread_serverhello 16384 $HEARTBLEED_MAX_WAITSOCK
     if [[ $? -eq 3 ]]; then
          append=", timed out"
          pr_svrty_best "not vulnerable (OK)"; out "$append"
          fileout "$jsonID" "OK" "not vulnerable $append" "$cve" "$cwe"
     else

          # server reply should be (>=SSLv3): 18030x in case of a heartBEAT reply -- which we take as a positive result
          tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE")
          debugme echo "tls_content_type: ${tls_hello_ascii:0:2}"
          debugme echo "tls_protocol: ${tls_hello_ascii:2:4}"

          lines_returned=$(count_lines "$(hexdump -ve '16/1 "%02x " " \n"' "$SOCK_REPLY_FILE")")
          debugme echo "lines HB reply: $lines_returned"

          if [[ $DEBUG -ge 3 ]]; then
               tmln_out "\nheartbleed reply: "
               hexdump -C "$SOCK_REPLY_FILE" | head -20
               [[ $lines_returned -gt 20 ]] && tmln_out "[...]"
               tmln_out
          fi

          if [[ $lines_returned -gt 1 ]] && [[ "${tls_hello_ascii:0:4}" == 1803 ]]; then
               if [[ "$STARTTLS_PROTOCOL" =~ ftp ]]; then
                    # check possibility of weird vsftpd reply, see #426, despite "1803" seems very unlikely...
                    if grep -q '500 OOPS' "$SOCK_REPLY_FILE" ; then
                         append=", successful weeded out vsftpd false positive"
                         pr_svrty_best "not vulnerable (OK)"; out "$append"
                         fileout "$jsonID" "OK" "not vulnerable $append" "$cve" "$cwe"
                    else
                         out "likely "
                         pr_svrty_critical "VULNERABLE (NOT ok)"
                         [[ $DEBUG -lt 3 ]] && tm_out ", use debug >=3 to confirm"
                         fileout "$jsonID" "CRITICAL" "VULNERABLE $cve" "$cwe" "$hint"
                    fi
               else
                    pr_svrty_critical "VULNERABLE (NOT ok)"
                    fileout "$jsonID" "CRITICAL" "VULNERABLE $cve" "$cwe" "$hint"
                    set_grade_cap "F" "Vulnerable to Heartbleed"
               fi
          else
               pr_svrty_best "not vulnerable (OK)"
               fileout "$jsonID" "OK" "not vulnerable $cve" "$cwe"
          fi
     fi
     outln
     tmpfile_handle ${FUNCNAME[0]}.dd $SOCK_REPLY_FILE
     close_socket
     return 0
}

# helper function
ok_ids(){
     prln_svrty_best "\n ok -- something reset our ccs packets"
     return 0
}

# see https://www.openssl.org/news/secadv_20140605.txt
# mainly adapted from Ramon de C Valle's C code from https://gist.github.com/rcvalle/71f4b027d61a78c42607
#FIXME: At a certain point ccs needs to be changed and make use of code2network using a file, then tls_sockets
#
run_ccs_injection(){
     local tls_hexcode ccs_message client_hello byte6
     local -i retval ret=0
     local tls_hello_ascii=""
     local jsonID="CCS"
     local cve="CVE-2014-0224"
     local cwe="CWE-310"
     local hint=""

     [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for CCS injection vulnerability " && outln
     pr_bold " CCS"; out " ($cve)                       "

     if ( [[ "$STARTTLS_PROTOCOL" =~ ldap ]] || [[ "$STARTTLS_PROTOCOL" =~ irc ]] ); then
          prln_local_problem "STARTTLS/$STARTTLS_PROTOCOL and --ssl-native collide here"
          return 1
     fi

     if [[ 0 -eq $(has_server_protocol tls1) ]]; then
          tls_hexcode="x03, x01"
     elif [[ 0 -eq $(has_server_protocol tls1_1) ]]; then
          tls_hexcode="x03, x02"
     elif [[ 0 -eq $(has_server_protocol tls1_2) ]]; then
          tls_hexcode="x03, x03"
     elif [[ 0 -eq $(has_server_protocol ssl3) ]]; then
          tls_hexcode="x03, x00"
     else # no protocol for some reason defined, determine TLS versions offered with a new handshake
          $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY") >$TMPFILE 2>$ERRFILE </dev/null
          case "$(get_protocol $TMPFILE)" in
               *1.2)  tls_hexcode="x03, x03" ; add_proto_offered tls1_2 yes ;;
               *1.1)  tls_hexcode="x03, x02" ; add_proto_offered tls1_1 yes ;;
               TLSv1) tls_hexcode="x03, x01" ; add_proto_offered tls1 yes ;;
               SSLv3) tls_hexcode="x03, x00" ; add_proto_offered ssl3 yes ;;
          esac
     fi
     debugme echo "using protocol $tls_hexcode"

     ccs_message=", x14, $tls_hexcode ,x00, x01, x01"

     client_hello="
     # TLS header (5 bytes)
     ,x16,                         # content type (x16 for handshake)
     x03, x01,                     # TLS version in record layer is always TLS 1.0 (except SSLv3)
     x00, x93,                     # length
     # Handshake header
     x01,                          # type (x01 for ClientHello)
     x00, x00, x8f,                # length
     $tls_hexcode,                 # TLS version
     # Random (32 byte)
     x53, x43, x5b, x90, x9d, x9b, x72, x0b,
     xbc, x0c, xbc, x2b, x92, xa8, x48, x97,
     xcf, xbd, x39, x04, xcc, x16, x0b, x85,
     x03, x90, x9f, x77, x04, x33, xd4, xde,
     x00,                # session ID length
     x00, x68,           # cipher suites length
     # Cipher suites (51 suites)
     xc0, x13, xc0, x12, xc0, x11, xc0, x10,
     xc0, x0f, xc0, x0e, xc0, x0d, xc0, x0c,
     xc0, x0b, xc0, x0a, xc0, x09, xc0, x08,
     xc0, x07, xc0, x06, xc0, x05, xc0, x04,
     xc0, x03, xc0, x02, xc0, x01, x00, x39,
     x00, x38, x00, x37, x00, x36, x00, x35, x00, x34,
     x00, x33, x00, x32, x00, x31, x00, x30,
     x00, x2f, x00, x16, x00, x15, x00, x14,
     x00, x13, x00, x12, x00, x11, x00, x10,
     x00, x0f, x00, x0e, x00, x0d, x00, x0c,
     x00, x0b, x00, x0a, x00, x09, x00, x08,
     x00, x07, x00, x06, x00, x05, x00, x04,
     x00, x03, x00, x02, x00, x01, x01, x00"

     fd_socket 5 || return 1

# we now make a standard handshake ...
     debugme echo -n "sending client hello... "
     socksend "$client_hello" 1

     debugme echo "reading server hello... "
     sockread_serverhello 32768
     if [[ $DEBUG -ge 4 ]]; then
          hexdump -C "$SOCK_REPLY_FILE" | head -20
          tmln_out "[...]"
          tm_out "\nsending payload #1 with TLS version $tls_hexcode:  "
     fi
     rm "$SOCK_REPLY_FILE"
# ... and then send the a change cipher spec message
     socksend "$ccs_message" 1 || ok_ids
     sockread_serverhello 4096 $CCS_MAX_WAITSOCK
     if [[ $DEBUG -ge 3 ]]; then
          tmln_out "\n1st reply: "
          hexdump -C "$SOCK_REPLY_FILE" | head -20
          tmln_out
          tm_out "sending payload #2 with TLS version $tls_hexcode:  "
     fi
     rm "$SOCK_REPLY_FILE"

     socksend "$ccs_message" 2 || ok_ids
     sockread_serverhello 4096 $CCS_MAX_WAITSOCK
     retval=$?

     tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE")
     byte6="${tls_hello_ascii:12:2}"
     debugme echo "tls_content_type: ${tls_hello_ascii:0:2} | tls_protocol: ${tls_hello_ascii:2:4} | byte6: $byte6"

     if [[ $DEBUG -ge 3 ]]; then
          tmln_out "\n2nd reply: "
          hexdump -C "$SOCK_REPLY_FILE"
          tmln_out
     fi

# in general, see https://en.wikipedia.org/wiki/Transport_Layer_Security#Alert_protocol
#                 https://tools.ietf.org/html/rfc5246#section-7.2
#
# not ok for CCSI:  15 | 0301    | 00 02    | 02 15
#                ALERT | TLS 1.0 | Length=2 | Decryption failed (21)
#
# ok:   nothing: ==> RST
#
# 0A:      Unexpected message
# 28:      Handshake failure
     if [[ -z "${tls_hello_ascii:0:12}" ]]; then
          # empty reply
          pr_svrty_best "not vulnerable (OK)"
          if [[ $retval -eq 3 ]]; then
               fileout "$jsonID" "OK" "not vulnerable (timed out)" "$cve" "$cwe"
          else
               fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe"
          fi
     elif [[ "${tls_hello_ascii:0:4}" == "1503" ]]; then
          if [[ ! "${tls_hello_ascii:5:2}" =~ [03|02|01|00] ]]; then
               pr_warning "test failed "
               out "no proper TLS repy (debug info: protocol sent: 1503${tls_hexcode#x03, x}, reply: ${tls_hello_ascii:0:14}"
               fileout "$jsonID" "DEBUG" "test failed, around line $LINENO, debug info (${tls_hello_ascii:0:14})" "$cve" "$cwe" "$hint"
               ret=1
          elif [[ "$byte6" == "15" ]]; then
               # decryption failed received
               pr_svrty_critical "VULNERABLE (NOT ok)"
               fileout "$jsonID" "CRITICAL" "VULNERABLE" "$cve" "$cwe" "$hint"
               set_grade_cap "F" "Vulnerable to CCS injection"
          elif [[ "$byte6" == "0A" ]] || [[ "$byte6" == "28" ]]; then
               # Unexpected message / Handshake failure  received
               pr_warning "likely "
               out "not vulnerable (OK)"
               out " - alert description type: $byte6"
               fileout "$jsonID" "WARN" "probably not vulnerable but received 0x${byte6} instead of 0x15" "$cve" "$cwe" "$hint"
          elif [[ "$byte6" == "14" ]]; then
               # bad_record_mac -- this is not "not vulnerable"
               out "likely "
               pr_svrty_critical "VULNERABLE (NOT ok)"
               out ", suspicious \"bad_record_mac\" ($byte6)"
               fileout "$jsonID" "CRITICAL" "likely VULNERABLE" "$cve" "$cwe" "$hint"
          else
               # other errors, see https://tools.ietf.org/html/rfc5246#section-7.2
               out "likely "
               pr_svrty_critical "VULNERABLE (NOT ok)"
               out ", suspicious error code \"$byte6\" returned. Please report"
               fileout "$jsonID" "CRITICAL" "likely VULNERABLE with $byte6" "$cve" "$cwe" "$hint"
          fi
     elif [[ $STARTTLS_PROTOCOL == "mysql" ]] && [[ "${tls_hello_ascii:14:12}" == "233038533031" ]]; then
          # MySQL community edition (yaSSL) returns a MySQL error instead of a TLS Alert
          # Error: #08S01 Bad handshake
          pr_svrty_best "not vulnerable (OK)"
          out ", looks like MySQL community edition (yaSSL)"
          fileout "$jsonID" "OK" "not vulnerable (MySQL community edition (yaSSL) detected)" "$cve" "$cwe"
     elif [[ "$byte6" == [0-9a-f][0-9a-f] ]] && [[ "${tls_hello_ascii:2:2}" != "03" ]]; then
          pr_warning "test failed"
          out ", probably read buffer too small (${tls_hello_ascii:0:14})"
          fileout "$jsonID" "DEBUG" "test failed, probably read buffer too small (${tls_hello_ascii:0:14})" "$cve" "$cwe" "$hint"
          ret=1
     else
          pr_warning "test failed "
          out "around line $LINENO (debug info: ${tls_hello_ascii:0:12},$byte6)"
          fileout "$jsonID" "DEBUG" "test failed, around line $LINENO, debug info (${tls_hello_ascii:0:12},$byte6)" "$cve" "$cwe" "$hint"
          ret=1
     fi
     outln

     tmpfile_handle ${FUNCNAME[0]}.dd $SOCK_REPLY_FILE
     close_socket
     return $ret
}

sub_session_ticket_tls() {
     local tls_proto="$1"
     local sessticket_tls=""
     #FIXME: we likely have done this already before (either @ run_server_defaults() or at least the output
     #       from a previous handshake) --> would save 1x connect. We have TLS_TICKET but not yet the ticket itself #FIXME
     #ATTENTION: we DO NOT use SNI here as we assume ticketbleed is a vulnerability of the TLS stack. If we'd do SNI here, we'd also need
     #           it in the ClientHello of run_ticketbleed() otherwise the ticket will be different and the whole thing won't work!
     #
     sessticket_tls="$($OPENSSL s_client $(s_client_options "$BUGS $tls_proto $PROXY -connect $NODEIP:$PORT") </dev/null 2>$ERRFILE | awk '/TLS session ticket:/,/^$/' | awk '!/TLS session ticket/')"
     sessticket_tls="$(sed -e 's/^.* - /x/g' -e 's/  .*$//g' <<< "$sessticket_tls" | tr '\n' ',')"
     sed -e 's/ /,x/g' -e 's/-/,x/g' <<< "$sessticket_tls"

}


# see https://blog.filippo.io/finding-ticketbleed/ |  https://filippo.io/ticketbleed/
run_ticketbleed() {
     local tls_hexcode tls_proto=""
     local session_tckt_tls=""
     local -i len_ch=300                            # fixed len of prepared clienthello below
     local sid="x00,x0B,xAD,xC0,xDE,x00,"           # some abitratry bytes
     local len_sid="$(( ${#sid} / 4))"
     local xlen_sid="$(dec02hex $len_sid)"
     local -i len_tckt_tls=0 nr_sid_detected=0
     local xlen_tckt_tls="" xlen_handshake_record_layer="" xlen_handshake_ssl_layer=""
     local -i len_handshake_record_layer=0
     local i
     local -a memory sid_detected
     local early_exit=true
     local -i ret=0
     local jsonID="ticketbleed"
     local cve="CVE-2016-9244"
     local cwe="CWE-200"
     local hint=""

     [[ -n "$STARTTLS" ]] && return 0
     [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for Ticketbleed vulnerability " && outln
     pr_bold " Ticketbleed"; out " ($cve), experiment.  "

     if [[ "$SERVICE" != HTTP ]] && ! "$CLIENT_AUTH"; then
          outln "--   (applicable only for HTTPS)"
          fileout "$jsonID" "INFO" "not applicable, not HTTP" "$cve" "$cwe"
          return 0
     fi

     # highly unlikely that it is NOT supported. We may loose time here but it's more solid
     [[ -z "$TLS_EXTENSIONS" ]] && determine_tls_extensions
     if [[ ! "${TLS_EXTENSIONS}" =~ "session ticket" ]]; then
          pr_svrty_best "not vulnerable (OK)"
          outln ", no session ticket extension"
          fileout "$jsonID" "OK" "no session ticket extension" "$cve" "$cwe"
          return 0
     fi

     if [[ 0 -eq $(has_server_protocol tls1) ]]; then
          tls_hexcode="x03, x01"; tls_proto="-tls1"
     elif [[ 0 -eq $(has_server_protocol tls1_1) ]]; then
          tls_hexcode="x03, x02"; tls_proto="-tls1_1"
     elif [[ 0 -eq $(has_server_protocol tls1_2) ]]; then
          tls_hexcode="x03, x03"; tls_proto="-tls1_2"
     elif [[ 0 -eq $(has_server_protocol ssl3) ]]; then
          tls_hexcode="x03, x00"; tls_proto="-ssl3"
     else # no protocol for some reason defined, determine TLS versions offered with a new handshake
          "$HAS_TLS13" && tls_proto="-no_tls1_3"
          $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS $tls_proto -connect $NODEIP:$PORT $PROXY") >$TMPFILE 2>$ERRFILE </dev/null
          case "$(get_protocol $TMPFILE)" in
               *1.2)  tls_hexcode="x03, x03"; tls_proto="-tls1_2" ; add_proto_offered tls1_2 yes ;;
               *1.1)  tls_hexcode="x03, x02"; tls_proto="-tls1_1" ; add_proto_offered tls1_1 yes ;;
               TLSv1) tls_hexcode="x03, x01"; tls_proto="-tls1" ; add_proto_offered tls1 yes ;;
               SSLv3) tls_hexcode="x03, x00"; tls_proto="-ssl3" ; add_proto_offered ssl3 yes ;;
          esac
     fi
     debugme echo "using protocol $tls_hexcode"

     session_tckt_tls="$(sub_session_ticket_tls "$tls_proto")"
     if [[ "$session_tckt_tls" == "," ]]; then
          pr_svrty_best "not vulnerable (OK)"
          outln ", no session tickets"
          fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe"
          debugme echo " session ticket TLS \"$session_tckt_tls\""
          return 0
     fi

     len_tckt_tls=${#session_tckt_tls}
     len_tckt_tls=$(( len_tckt_tls / 4))
     xlen_tckt_tls="$(dec02hex $len_tckt_tls)"
     len_handshake_record_layer="$(( len_sid + len_ch + len_tckt_tls ))"
     xlen_handshake_record_layer="$(dec04hex "$len_handshake_record_layer")"
     len_handshake_ssl_layer="$(( len_handshake_record_layer + 4 ))"
     xlen_handshake_ssl_layer="$(dec04hex "$len_handshake_ssl_layer")"

     if [[ "$DEBUG" -ge 4 ]]; then
          echo "len_tckt_tls (hex):            $len_tckt_tls ($xlen_tckt_tls)"
          echo "sid:                           $sid"
          echo "len_sid (hex)                  $len_sid ($xlen_sid)"
          echo "len_handshake_record_layer:    $len_handshake_record_layer ($xlen_handshake_record_layer)"
          echo "len_handshake_ssl_layer:       $len_handshake_ssl_layer ($xlen_handshake_ssl_layer)"
          echo "session_tckt_tls:              $session_tckt_tls"
     fi

     client_hello="
     # TLS header (5 bytes)
     ,x16,               # Content type (x16 for handshake)
     x03,x01,            # TLS version record layer
                         # Length Secure Socket Layer follows:
     $xlen_handshake_ssl_layer,
     # Handshake header
     x01,                # Type (x01 for ClientHello)
                         # Length of ClientHello follows:
     x00, $xlen_handshake_record_layer,
     $tls_hexcode,        # TLS Version
     # Random (32 byte) Unix time etc, see www.moserware.com/2009/06/first-few-milliseconds-of-https.html
     xee, xee, x5b, x90, x9d, x9b, x72, x0b,
     xbc, x0c, xbc, x2b, x92, xa8, x48, x97,
     xcf, xbd, x39, x04, xcc, x16, x0b, x85,
     x03, x90, x9f, x77, x04, x33, xff, xff,
     $xlen_sid,          # Session ID length
     $sid
     x00, x6a,           # Cipher suites length 106
     # 53 Cipher suites
     xc0,x14, xc0,x13, xc0,x0a, xc0,x21,
     x00,x39, x00,x38, x00,x88, x00,x87,
     xc0,x0f, xc0,x05, x00,x35, x00,x84,
     xc0,x12, xc0,x08, xc0,x1c, xc0,x1b,
     x00,x16, x00,x13, xc0,x0d, xc0,x03,
     x00,x0a, xc0,x13, xc0,x09, xc0,x1f,
     xc0,x1e, x00,x33, x00,x32, x00,x9a,
     x00,x99, x00,x45, x00,x44, xc0,x0e,
     xc0,x04, x00,x2f, x00,x96, x00,x41,
     xc0,x11, xc0,x07, xc0,x0c, xc0,x02,
     x00,x05, x00,x04, x00,x15, x00,x12,
     xc0,x30, xc0,x2f, x00,x9d, x00,x9c,
     x00,x3d, x00,x3c, x00,x9f, x00,x9e,
     x00,xff,
     x01,               # Compression methods length
     x00,               # Compression method (x00 for NULL)
     x01,x5b,           # Extensions length    ####### 10b + x14 + x3c
# Extension Padding
     x00,x15,
     # length:
     x00,x38,
     x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00,
     x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00,
     x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00, x00,x00,
# Extension: ec_point_formats
     x00,x0b,
     # length:
     x00,x04,
     # data:
     x03,x00, x01,x02,
# Extension: elliptic_curves
     x00,x0a,
     # length
     x00,x34,
     x00,x32,
     # data:
     x00,x0e, x00,x0d, x00,x19, x00,x0b, x00,x0c,
     x00,x18, x00,x09, x00,x0a, x00,x16,
     x00,x17, x00,x08, x00,x06, x00,x07,
     x00,x14, x00,x15, x00,x04, x00,x05,
     x00,x12, x00,x13, x00,x01, x00,x02,
     x00,x03, x00,x0f, x00,x10, x00,x11,
# Extension: Signature Algorithms
     x00,x0d,
     # length:
     x00,x10,
     # data:
     x00,x0e ,x04,x01, x05,x01 ,x02,x01, x04,x03, x05,x03,
     x02,x03, x02,x02,
# Extension: SessionTicket TLS
     x00, x23,
     # length of SessionTicket TLS
     x00, $xlen_tckt_tls,
     # data, Session Ticket
     $session_tckt_tls                       # here we have the comma already
# Extension: Heartbeat
     x00, x0f, x00, x01, x01"

     # we do 3 client hellos, then see whether different memory is returned
     for i in 1 2 3; do
          fd_socket 5 || return 6
          debugme echo -n "sending client hello... "
          socksend "$client_hello" 0

          debugme echo "reading server hello (ticketbleed reply)... "
          if "$FAST_SOCKET"; then
               tls_hello_ascii=$(sockread_fast 32768)
          else
               sockread_serverhello 32768 $CCS_MAX_WAITSOCK
               tls_hello_ascii=$(hexdump -v -e '16/1 "%02X"' "$SOCK_REPLY_FILE")
          fi
          [[ "$DEBUG" -ge 5 ]] && echo "$tls_hello_ascii"
          if [[ "$DEBUG" -ge 4 ]]; then
               echo "============================="
               echo "$tls_hello_ascii"
               echo "============================="
          fi

          if [[ "${tls_hello_ascii:0:2}" == 15 ]]; then
               debugme echo -n "TLS Alert ${tls_hello_ascii:10:4} (TLS version: ${tls_hello_ascii:2:4}) -- "
               pr_svrty_best "not vulnerable (OK)"
               fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe"
               send_close_notify "${tls_hello_ascii:18:4}"
               close_socket
               break
          elif [[ -z "${tls_hello_ascii:0:2}" ]]; then
               pr_svrty_best "not vulnerable (OK)"
               out ", reply empty"
               fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe"
               send_close_notify "${tls_hello_ascii:18:4}"
               close_socket
               break
          elif [[ "${tls_hello_ascii:0:2}" == 16 ]]; then
               early_exit=false
               debugme echo -n "Handshake (TLS version: ${tls_hello_ascii:2:4}), "
               if [[ "${tls_hello_ascii:10:6}" == 020000 ]]; then
                    debugme echo -n "ServerHello -- "
               else
                    debugme echo -n "Message type: ${tls_hello_ascii:10:6} -- "
               fi
               sid_input=$(sed -e 's/x//g' -e 's/,//g' <<< "$sid")
               sid_detected[i]="${tls_hello_ascii:88:32}"
               memory[i]="${tls_hello_ascii:$((88+ len_sid*2)):$((32 - len_sid*2))}"
               if [[ "$DEBUG" -ge 3 ]]; then
                    echo
                    echo "TLS version, record layer: ${tls_hello_ascii:18:4}"
                    echo "Session ID:                ${sid_detected[i]}"
                    echo "memory:                    ${memory[i]}"
                    echo -n "$sid_input in SID:       " ;
                         [[ "${sid_detected[i]}"  =~ $sid_input ]] && echo "yes" || echo "no"
               fi
               [[ "$DEBUG" -ge 1 ]] && echo $tls_hello_ascii >$TEMPDIR/${FUNCNAME[0]}.tls_hello_ascii${i}.txt
          else
               ret=1
               pr_warning "test failed"
               out " around line $LINENO (debug info: ${tls_hello_ascii:0:2}, ${tls_hello_ascii:2:10})"
               fileout "$jsonID" "DEBUG" "test failed, around $LINENO (debug info: ${tls_hello_ascii:0:2}, ${tls_hello_ascii:2:10})" "$cve" "$cwe"
               send_close_notify "${tls_hello_ascii:18:4}"
               close_socket
               break
          fi
          send_close_notify "${tls_hello_ascii:18:4}"
          close_socket
     done

     if ! "$early_exit"; then
          # here we test the replies if a TLS server hello was received >1x
          for i in 1 2 3 ; do
               if [[ "${sid_detected[i]}" =~ $sid_input ]]; then
                    # was our faked TLS SID returned?
                    nr_sid_detected+=1
               fi
          done
          if [[ $nr_sid_detected -eq 3 ]]; then
               if [[ ${memory[1]} != ${memory[2]} ]] && [[ ${memory[2]} != ${memory[3]} ]]; then
                    pr_svrty_critical "VULNERABLE (NOT ok)"
                    fileout "$jsonID" "CRITICAL" "VULNERABLE" "$cve" "$cwe" "$hint"
                    set_grade_cap "F" "Vulnerable to Ticketbleed"
               else
                    pr_svrty_best "not vulnerable (OK)"
                    out ", session IDs were returned but potential memory fragments do not differ"
                    fileout "$jsonID" "OK" "not vulnerable, returned potential memory fragments do not differ" "$cve" "$cwe"
               fi
          else
               if [[ "$DEBUG" -ge 2 ]]; then
                    echo
                    pr_warning "test failed, non reproducible results!"
               else
                    pr_warning "test failed, non reproducible results!"
                    out " Please run again w \"--debug=2\"  (# of faked TLS SIDs detected: $nr_sid_detected)"
               fi
               fileout "$jsonID" "DEBUG" "test failed, non reproducible results. $nr_sid_detected TLS Session IDs $nr_sid_detected, ${sid_detected[1]},${sid_detected[2]},${sid_detected[3]}" "$cve" "$cwe"
               ret=1
          fi
     fi
     outln
     return $ret
}

# Overview @ http://www.exploresecurity.com/wp-content/uploads/custom/SSL_manual_cheatsheet.html
#
run_renego() {
     local legacycmd="" proto="$OPTIMAL_PROTO"
     local sec_renego sec_client_renego
     local -i ret=0
     local cve=""
     local cwe="CWE-310"
     local hint=""
     local jsonID=""
     # No SNI needed here as there won't be two different SSL stacks for one IP

     "$HAS_TLS13" && [[ -z "$proto" ]] && proto="-no_tls1_3"

     [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for Renegotiation vulnerabilities " && outln

     pr_bold " Secure Renegotiation (RFC 5746)           "
     jsonID="Secure renegotiation"

     if "$TLS13_ONLY"; then
          # https://www.openssl.org/blog/blog/2018/02/08/tlsv1.3/
          pr_svrty_best "not vulnerable (OK)"
          [[ $DEBUG -ge 1 ]] && out ", no renegotiation support in TLS 1.3 only servers"
          outln
          fileout "$jsonID" "OK" "TLS 1.3 only server" "$cve" "$cwe"
     else
          # first fingerprint for the Line "Secure Renegotiation IS NOT" or "Secure Renegotiation IS "
          $OPENSSL s_client $(s_client_options "$proto $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY") 2>&1 </dev/null >$TMPFILE 2>$ERRFILE
          if sclient_connect_successful $? $TMPFILE; then
               grep -iaq "Secure Renegotiation IS NOT" $TMPFILE
               sec_renego=$?                                                    # 0= Secure Renegotiation IS NOT supported
               # grep -iaq "Secure Renegotiation IS supported"
               #FIXME: didn't occur to me yet but why not also to check on "Secure Renegotiation IS supported"
               case $sec_renego in
                    0)   prln_svrty_critical "Not supported / VULNERABLE (NOT ok)"
                         fileout "$jsonID" "CRITICAL" "The SSL renegotiation flaw can affect different types of systems. It is essentially caused by a vulnerability in the client-initiated renegotiation of SSL/TLS for existing server connections.\nThe negotiation process of the SSL encryption uses significantly more resources on the server than on the client. Therefore, if the client can initiate the renegotiation process, an attacker can render the server unavailable with a Denial of Service attack.\nRecommendation:\nYou must manually disable these configuration options if your web server does not prevent client-initiated SSL renegotiation by default. The guiding principle is that only the server should be allowed to initiate a renegotiation of the SSL/TLS connection.\nIn some cases, disabling a client renegotiation attempt may not be possible. Then it is crucial to set only secure renegotiation and define the number of possible SSL handshakes.\n" "$cve" "$cwe" "$hint"
                         set_grade_warning "Secure renegotiation is not supported"
                         ;;
                    1)   prln_svrty_best "supported (OK)"
                         fileout "$jsonID" "OK" "supported" "$cve" "$cwe"
                         ;;
                    *)   prln_warning "FIXME (bug): $sec_renego"
                         fileout "$jsonID" "WARN" "FIXME (bug) $sec_renego" "$cve" "$cwe"
                         ;;
               esac
          else
               prln_warning "OpenSSL handshake didn't succeed"
               fileout "$jsonID" "WARN" "OpenSSL handshake didn't succeed" "$cve" "$cwe"
          fi
     fi

     # FIXME: Basically this can be done with sockets and we might have that information already
     # see https://tools.ietf.org/html/rfc5746#section-3.4: 'The client MUST include either an empty "renegotiation_info"
     # extension, or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the ClientHello. [..]
     # When a ServerHello is received, the client MUST check if it includes the "renegotiation_info" extension:
     # If the extension is not present, the server does not support secure renegotiation'


     pr_bold " Secure Client-Initiated Renegotiation     "
     jsonID="secure_client_renego"
     cve="CVE-2011-1473"
     # see: https://blog.qualys.com/ssllabs/2011/10/31/tls-renegotiation-and-denial-of-service-attacks
     #      https://blog.ivanristic.com/2009/12/testing-for-ssl-renegotiation.html -- head/get doesn't seem to be needed though
     #      https://archive.fo/20130415224936/http://www.thc.org/thc-ssl-dos/
     #      https://vincent.bernat.ch/en/blog/2011-ssl-dos-mitigation
     case "$OSSL_VER" in
          0.9.8*)             # we need this for Mac OSX unfortunately
               case "$OSSL_VER_APPENDIX" in
                    [a-l])
                         prln_local_problem " Your $OPENSSL cannot test this secure renegotiation vulnerability"
                         fileout "$jsonID" "WARN" "your $OPENSSL cannot test this secure renegotiation vulnerability" "$cve" "$cwe"
                         return 1
                         ;;
                    [m-z])
                         ;; # all ok
               esac
               ;;
          1.0.1*|1.0.2*)
               legacycmd="-legacy_renegotiation"
               ;;
          0.9.9*|1.0*|1.1*)
               ;;   # all ok
     esac


     if "$TLS13_ONLY"; then
          pr_svrty_best "not vulnerable (OK)"
          [[ $DEBUG -ge 1 ]] && out ", no renegotiation support in TLS 1.3 only servers"
          outln
          fileout "$jsonID" "OK" "not vulnerable, TLS 1.3 only" "$cve" "$cwe"
     elif "$CLIENT_AUTH"; then
          prln_warning "client x509-based authentication prevents this from being tested"
          fileout "$jsonID" "WARN" "client x509-based authentication prevents this from being tested"
          sec_client_renego=1
     else
          # We need up to two tries here, as some LiteSpeed servers don't answer on "R" and block. Thus first try in the background
          # msg enables us to look deeper into it while debugging
          echo R | $OPENSSL s_client $(s_client_options "$proto $BUGS $legacycmd $STARTTLS -connect $NODEIP:$PORT $PROXY") >$TMPFILE 2>>$ERRFILE &
          wait_kill $! $HEADER_MAXSLEEP
          if [[ $? -eq 3 ]]; then
               pr_svrty_good "likely not vulnerable (OK)"; outln ", timed out"        # it hung
               fileout "$jsonID" "OK" "likely not vulnerable (timed out)" "$cve" "$cwe"
               sec_client_renego=1
          else
               # second try in the foreground as we are sure now it won't hang
               echo R | $OPENSSL s_client $(s_client_options "$proto $legacycmd $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY") >$TMPFILE 2>>$ERRFILE
               sec_client_renego=$?                                                  # 0=client is renegotiating & doesn't return an error --> vuln!
               case "$sec_client_renego" in
                    0)   # We try again if server is HTTP. This could be either a node.js server or something else.
                         # node.js has a mitigation which allows 3x R and then blocks. So we test 4x
                         # This way we save a couple seconds as we weeded out the ones which are more robust
                         if [[ $SERVICE != HTTP ]]; then
                              pr_svrty_medium "VULNERABLE (NOT ok)"; outln ", potential DoS threat"
                              fileout "$jsonID" "MEDIUM" "VULNERABLE, potential DoS threat" "$cve" "$cwe" "$hint"
                         else
                              (for i in {1..4}; do echo R; sleep 1; done) | \
                                   $OPENSSL s_client $(s_client_options "$proto $legacycmd $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY") >$TMPFILE 2>>$ERRFILE
                              case $? in
                                   0) pr_svrty_high "VULNERABLE (NOT ok)"; outln ", DoS threat"
                                      fileout "$jsonID" "HIGH" "VULNERABLE, DoS threat" "$cve" "$cwe" "$hint"
                                      ;;
                                   1) pr_svrty_good "not vulnerable (OK)"
                                      outln " -- mitigated"
                                      fileout "$jsonID" "OK" "not vulnerable, mitigated" "$cve" "$cwe"
                                      ;;
                                   *) prln_warning "FIXME (bug): $sec_client_renego (4 tries)"
                                      fileout "$jsonID" "DEBUG" "FIXME (bug 4 tries) $sec_client_renego" "$cve" "$cwe"
                                      ret=1
                                      ;;
                              esac
                         fi
                         ;;
                    1)
                         prln_svrty_good "not vulnerable (OK)"
                         fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe"
                         ;;
                    *)
                         prln_warning "FIXME (bug): $sec_client_renego"
                         fileout "$jsonID" "DEBUG" "FIXME (bug) $sec_client_renego - Please report" "$cve" "$cwe"
                         ret=1
                         ;;
               esac
          fi
     fi

     #pr_bold " Insecure Client-Initiated Renegotiation  "  # pre-RFC 5746, CVE-2009-3555
     #jsonID="insecure_client_renego"
     #
     # https://www.openssl.org/news/vulnerabilities.html#y2009. It can only be tested with OpenSSL <=0.9.8k
     # Insecure Client-Initiated Renegotiation is missing ==> sockets. When we complete the handshake ;-)

     tmpfile_handle ${FUNCNAME[0]}.txt
     return $ret
}

run_crime() {
     local -i ret=0 sclient_success
     local addcmd=""
     local cve="CVE-2012-4929"
     local cwe="CWE-310"
     local hint=""

     # In a nutshell: don't offer TLS/SPDY compression. This tests for CRIME Vulnerability on HTTPS only,
     # not SPDY or ALPN (yet). Please note that it is an attack where you need client side control, so in
     # regular situations this # means anyway "game over", with or without CRIME.
     #
     # https://blog.qualys.com/ssllabs/2012/09/14/crime-information-leakage-attack-against-ssltls

     [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for CRIME vulnerability " && outln
     pr_bold " CRIME, TLS " ; out "($cve)                "

     if "$TLS13_ONLY"; then
          pr_svrty_best "not vulnerable (OK)"
          [[ $DEBUG -ge 1 ]] && out ", no compression in TLS 1.3 only servers"
          outln
          fileout "$jsonID" "OK" "TLS 1.3 only server" "$cve" "$cwe"
          return 0
     fi

     if ! "$HAS_ZLIB"; then
          if "$SSL_NATIVE"; then
               prln_local_problem "$OPENSSL lacks zlib support"
               fileout "CRIME_TLS" "WARN" "CRIME, TLS: Not tested. $OPENSSL lacks zlib support" "$cve" "$cwe"
               return 1
          else
               tls_sockets "03" "$TLS12_CIPHER" "" "" "true"
               sclient_success=$?
               [[ $sclient_success -eq 2 ]] && sclient_success=0
               [[ $sclient_success -eq 0 ]] && cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" $TMPFILE
          fi
     else
          [[ "$OSSL_VER" == 0.9.8* ]] && addcmd="-no_ssl2"
          "$HAS_TLS13" && [[ -z "$OPTIMAL_PROTO" ]] && addcmd+=" -no_tls1_3"
          $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS -comp $addcmd $STARTTLS -connect $NODEIP:$PORT $PROXY $SNI") </dev/null &>$TMPFILE
          sclient_connect_successful $? $TMPFILE
          sclient_success=$?
     fi

     if [[ $sclient_success -ne 0 ]]; then
          pr_warning "test failed (couldn't connect)"
          fileout "CRIME_TLS" "WARN" "Check failed, couldn't connect" "$cve" "$cwe"
          ret=1
     elif grep -a Compression $TMPFILE | grep -aq NONE >/dev/null; then
          pr_svrty_good "not vulnerable (OK)"
          if [[ $SERVICE != HTTP ]] && ! "$CLIENT_AUTH";  then
               out " (not using HTTP anyway)"
               fileout "CRIME_TLS" "OK" "not vulnerable (not using HTTP anyway)" "$cve" "$cwe"
          else
               fileout "CRIME_TLS" "OK" "not vulnerable" "$cve" "$cwe"
          fi
     else
          if [[ $SERVICE == HTTP ]] || "$CLIENT_AUTH"; then
               pr_svrty_high "VULNERABLE (NOT ok)"
               fileout "CRIME_TLS" "HIGH" "VULNERABLE" "$cve" "$cwe" "$hint"
          else
               pr_svrty_medium "VULNERABLE but not using HTTP: probably no exploit known"
               fileout "CRIME_TLS" "MEDIUM" "VULNERABLE, but not using HTTP. Probably no exploit known" "$cve" "$cwe" "$hint"
               # not clear whether a protocol != HTTP offers the ability to repeatedly modify the input
               # which is done e.g. via javascript in the context of HTTP
          fi
          set_grade_cap "C" "Vulnerable to CRIME"
     fi
     outln

# this needs to be re-done i order to remove the redundant check for spdy

     # weed out starttls, spdy-crime is a web thingy
#    if [[ "x$STARTTLS" != "x" ]]; then
#         echo
#         return $ret
#    fi

     # weed out non-webports, spdy-crime is a web thingy. there's a catch thoug, you see it?
#    case $PORT in
#         25|465|587|80|110|143|993|995|21)
#         echo
#         return $ret
#    esac

#    if "$HAS_NPN"; then
#         $OPENSSL s_client -host $NODE -port $PORT -nextprotoneg $NPN_PROTOs  $SNI </dev/null 2>/dev/null >$TMPFILE
#         if [[ $? -eq 0 ]]; then
#              echo
#              pr_bold "CRIME Vulnerability, SPDY " ; outln "($cve): "

#              STR=$(grep Compression $TMPFILE )
#              if echo $STR | grep -q NONE >/dev/null; then
#                   pr_svrty_best "not vulnerable (OK)"
#                   ret=$((ret + 0))
#              else
#                   pr_svrty_critical "VULNERABLE (NOT ok)"
#                   ret=$((ret + 1))
#              fi
#         fi
#    fi
#    [[ $DEBUG -ge 2 ]] tmln_out "$STR"
     tmpfile_handle ${FUNCNAME[0]}.txt
     return $ret
}


# BREACH is a HTTP-level compression & an attack which works against any cipher suite and is agnostic
# to the version of TLS/SSL, more: http://www.breachattack.com/ . Foreign referrers are the important thing here!
# Mitigation: see https://community.qualys.com/message/20360
#
run_breach() {
     local header
     local -i ret=0
     local -i was_killed=0
     local referer useragent
     local url="$1"
     local spaces="                                          "
     local disclaimer=""
     local when_makesense=" Can be ignored for static pages or if no secrets in the page"
     local cve="CVE-2013-3587"
     local cwe="CWE-310"
     local hint=""
     local jsonID="BREACH"

     [[ $SERVICE != HTTP ]] && ! "$CLIENT_AUTH" && return 7

     [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for BREACH (HTTP compression) vulnerability " && outln
     pr_bold " BREACH"; out " ($cve)                    "
     if "$CLIENT_AUTH"; then
          outln "cannot be tested (server side requires x509 authentication)"
          fileout "$jsonID" "INFO" "was not tested, server side requires x509 authentication" "$cve" "$cwe"
     fi

     # if [[ $NR_HEADER_FAIL -ge $MAX_HEADER_FAIL ]]; then
     #      pr_warning "Retrieving HTTP header failed before. Skipping."
     #      fileout "$jsonID" "WARN" "HTTP response was wampty before" "$cve" "$cwe"
     #      outln
     #      return 1
     # fi

     [[ -z "$url" ]] && url="/"
     disclaimer=" - only supplied \"$url\" tested"

     referer="https://google.com/"
     [[ "$NODE" =~ google ]] && referer="https://yandex.ru/"     # otherwise we have a false positive for google.com
     useragent="$UA_STD"
     $SNEAKY && useragent="$UA_SNEAKY"
     tm_out "GET $url HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $useragent\r\nReferer: $referer\r\nConnection: Close\r\nAccept-encoding: gzip,deflate,compress\r\nAccept: text/*\r\n\r\n" | $OPENSSL s_client $(s_client_options "$OPTIMAL_PROTO $BUGS -quiet -ign_eof -connect $NODEIP:$PORT $PROXY $SNI") 1>$TMPFILE 2>$ERRFILE &
     wait_kill $! $HEADER_MAXSLEEP
     was_killed=$?                           # !=0 was killed
     result=$(awk '/^Content-Encoding/ { print $2 }' $TMPFILE)
     result=$(strip_lf "$result")
     debugme grep '^Content-Encoding' $TMPFILE
     if [[ ! -s $TMPFILE ]]; then
          pr_warning "failed (HTTP header request stalled or empty return"
          if [[ $was_killed -ne 0 ]]; then
               pr_warning " and was terminated"
               fileout "$jsonID" "WARN" "Test failed as HTTP request stalled and was terminated" "$cve" "$cwe"
          else
               fileout "$jsonID" "WARN" "Test failed as HTTP response was empty" "$cve" "$cwe"
          fi
          prln_warning ") "
          ret=1
     elif [[ -z $result ]]; then
          pr_svrty_best "no HTTP compression (OK) "
          outln "$disclaimer"
          fileout "$jsonID" "OK" "not vulnerable, no HTTP compression $disclaimer" "$cve" "$cwe"
     else
          pr_svrty_high "potentially NOT ok, uses $result HTTP compression."
          outln "$disclaimer"
          outln "$spaces$when_makesense"
          fileout "$jsonID" "HIGH" "potentially VULNERABLE, uses $result HTTP compression $disclaimer" "$cve" "$cwe" "$hint"
     fi
     # Any URL can be vulnerable. I am testing now only the given URL!

     tmpfile_handle ${FUNCNAME[0]}.txt
     return $ret
}


# SWEET32 (https://sweet32.info/). Birthday attacks on 64-bit block ciphers.
# In a nutshell: don't use 3DES ciphers anymore (DES, RC2 and IDEA too).
# Please note as opposed to RC4 (stream cipher) RC2 is a block cipher.
#
run_sweet32() {
     local -i sclient_success=1
     local sweet32_ciphers="IDEA-CBC-SHA:IDEA-CBC-MD5:RC2-CBC-MD5:KRB5-IDEA-CBC-SHA:KRB5-IDEA-CBC-MD5:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:SRP-DSS-3DES-EDE-CBC-SHA:SRP-RSA-3DES-EDE-CBC-SHA:SRP-3DES-EDE-CBC-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DH-RSA-DES-CBC3-SHA:DH-DSS-DES-CBC3-SHA:AECDH-DES-CBC3-SHA:ADH-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:DES-CBC3-MD5:DES-CBC3-SHA:RSA-PSK-3DES-EDE-CBC-SHA:PSK-3DES-EDE-CBC-SHA:KRB5-DES-CBC3-SHA:KRB5-DES-CBC3-MD5:ECDHE-PSK-3DES-EDE-CBC-SHA:DHE-PSK-3DES-EDE-CBC-SHA:DES-CFB-M1:EXP1024-DHE-DSS-DES-CBC-SHA:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DH-RSA-DES-CBC-SHA:DH-DSS-DES-CBC-SHA:ADH-DES-CBC-SHA:EXP1024-DES-CBC-SHA:DES-CBC-SHA:EXP1024-RC2-CBC-MD5:DES-CBC-MD5:DES-CBC-SHA:KRB5-DES-CBC-SHA:KRB5-DES-CBC-MD5:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-ADH-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC2-CBC-MD5:EXP-KRB5-RC2-CBC-SHA:EXP-KRB5-DES-CBC-SHA:EXP-KRB5-RC2-CBC-MD5:EXP-KRB5-DES-CBC-MD5:EXP-DH-DSS-DES-CBC-SHA:EXP-DH-RSA-DES-CBC-SHA"
     local sweet32_ciphers_hex="00,07, 00,21, 00,25, c0,12, c0,08, c0,1c, c0,1b, c0,1a, 00,16, 00,13, 00,10, 00,0d, c0,17, 00,1b, c0,0d, c0,03, 00,0a, 00,93, 00,8b, 00,1f, 00,23, c0,34, 00,8f, fe,ff, ff,e0, 00,63, 00,15, 00,12, 00,0f, 00,0c, 00,1a, 00,62, 00,09, 00,61, 00,1e, 00,22, fe,fe, ff,e1, 00,14, 00,11, 00,19, 00,08, 00,06, 00,27, 00,26, 00,2a, 00,29, 00,0b, 00,0e"
     local ssl2_sweet32_ciphers='RC2-CBC-MD5:EXP-RC2-CBC-MD5:IDEA-CBC-MD5:DES-CBC-MD5:DES-CBC-SHA:DES-CBC3-MD5:DES-CBC3-SHA:DES-CFB-M1'
     local ssl2_sweet32_ciphers_hex='03,00,80, 04,00,80, 05,00,80, 06,00,40, 06,01,40, 07,00,C0, 07,01,C0, FF,80,00'
     local nr_cipher_minimal=21
     local proto
     local cve="CVE-2016-2183 CVE-2016-6329"
     local cwe="CWE-327"
     local hint=""
     local -i nr_sweet32_ciphers=0 nr_supported_ciphers=0 nr_ssl2_sweet32_ciphers=0 nr_ssl2_supported_ciphers=0
     local ssl2_sweet=false
     local using_sockets=true
     local tls1_1_vulnerable=false

     [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for SWEET32 (Birthday Attacks on 64-bit Block Ciphers)       " && outln
     pr_bold " SWEET32"; out " (${cve// /, })    "

     if "$TLS13_ONLY"; then
          # Unfortunately there's no restriction using TLS 1.2 with $sweet32_ciphers
          pr_svrty_best "not vulnerable (OK)"
          [[ $DEBUG -ge 1 ]] && out ", TLS 1.3 doesn't offer such ciphers"
          outln
          fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe"
          return 0
     fi

     "$SSL_NATIVE" && using_sockets=false
     # The openssl binary distributed has almost everything we need (PSK, KRB5 ciphers and feff, ffe0 are typically missing).
     # Measurements show that there's little impact whether we use sockets or TLS here, so the default is sockets here.
     if "$using_sockets"; then
          for proto in 03 02 01 00; do
               [[ $(has_server_protocol "$proto") -eq 1 ]] && continue
               tls_sockets "$proto" "${sweet32_ciphers_hex}, 00,ff"
               sclient_success=$?
               [[ $sclient_success -eq 2 ]] && sclient_success=0
               [[ $sclient_success -eq 0 ]] && break
          done
          if [[ 1 -ne $(has_server_protocol "ssl2") ]]; then
               sslv2_sockets "$ssl2_sweet32_ciphers_hex"
               case $? in
                    3)   ssl2_sweet=true
                         add_proto_offered ssl2 yes ;;
                    0)   ;; # ssl2_sweet=false
                    1|4|6|7)  debugme "${FUNCNAME[0]}: test problem we don't handle here"
                         ;;
               esac
          fi
     else
          nr_sweet32_ciphers=$(count_ciphers $sweet32_ciphers)
          nr_supported_ciphers=$(count_ciphers $(actually_supported_osslciphers $sweet32_ciphers))
          debugme echo "$nr_sweet32_ciphers / $nr_supported_ciphers"

          nr_ssl2_sweet32_ciphers=$(count_ciphers $ssl2_sweet32_ciphers)
          nr_ssl2_supported_ciphers=$(count_ciphers $(actually_supported_osslciphers $ssl2_sweet32_ciphers))
          debugme echo "$nr_ssl2_sweet32_ciphers / $nr_ssl2_supported_ciphers"

          if [[ $(( nr_supported_ciphers + nr_ssl2_supported_ciphers )) -le $nr_cipher_minimal ]]; then
               pr_local_problem "Only ${nr_supported_ciphers}+${nr_ssl2_supported_ciphers} \"SWEET32 ciphers\" found in your $OPENSSL."
               outln " Test skipped"
               fileout "SWEET32" "WARN" "Not tested, lack of local support ($((nr_supported_ciphers + nr_ssl2_supported_ciphers)) ciphers only)" "$cve" "$cwe" "$hint"
               return 1
          fi
          for proto in -no_ssl2 -tls1_1 -tls1 -ssl3; do
               [[ $nr_supported_ciphers -eq 0 ]] && break
               ! "$HAS_SSL3" && [[ "$proto" == -ssl3 ]] && continue
               if [[ "$proto" != -no_ssl2 ]]; then
                    "$FAST" && break
                    [[ $(has_server_protocol "${proto:1}") -eq 1 ]] && continue
               fi
               $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS $proto -cipher $sweet32_ciphers -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null
               sclient_connect_successful $? $TMPFILE
               sclient_success=$?
               [[ $DEBUG -ge 2 ]] && grep -Eq "error|failure" $ERRFILE | grep -Eav "unable to get local|verify error"
               [[ $proto == -tls1_1 && $sclient_success -eq 0 ]] && tls1_1_vulnerable=true
               [[ $sclient_success -eq 0 ]] && break
          done
          if "$HAS_SSL2"; then
               if [[ 1 -ne $(has_server_protocol "ssl2") ]]; then
                    $OPENSSL s_client $STARTTLS $BUGS -ssl2 -cipher $ssl2_sweet32_ciphers -connect $NODEIP:$PORT $PROXY >$TMPFILE 2>$ERRFILE </dev/null
                    sclient_connect_successful $? $TMPFILE
                    if [[ $? -eq 0 ]]; then
                         ssl2_sweet=true
                         add_proto_offered ssl2 yes
                    fi
               fi
          else
               debugme tm_warning "Can't test with SSLv2 here as $OPENSSL lacks support"
               #  we omit adding a string for DEBUG==0 here as using sockets is the default and the following elif statement becomes ugly
          fi
     fi
     if [[ $sclient_success -eq 0 ]] && "$ssl2_sweet" ; then
          pr_svrty_low "VULNERABLE"; out ", uses 64 bit block ciphers for SSLv2 and above"
          fileout "SWEET32" "LOW" "uses 64 bit block ciphers for SSLv2 and above" "$cve" "$cwe" "$hint"
          "$tls1_1_vulnerable" && set_grade_cap "C" "Uses 64 bit block ciphers with TLS 1.1 (vulnerable to SWEET32)"
     elif [[ $sclient_success -eq 0 ]]; then
          pr_svrty_low "VULNERABLE"; out ", uses 64 bit block ciphers"
          fileout "SWEET32" "LOW" "uses 64 bit block ciphers" "$cve" "$cwe" "$hint"
          "$tls1_1_vulnerable" && set_grade_cap "C" "Uses 64 bit block ciphers with TLS 1.1 (vulnerable to SWEET32)"
     elif "$ssl2_sweet"; then
          pr_svrty_low "VULNERABLE"; out ", uses 64 bit block ciphers wth SSLv2 only"
          fileout "SWEET32" "LOW" "uses 64 bit block ciphers with SSLv2 only" "$cve" "$cwe" "$hint"
     else
          pr_svrty_best "not vulnerable (OK)";
          if "$using_sockets"; then
               fileout "SWEET32" "OK" "not vulnerable" "$cve" "$cwe"
          else
               if [[ "$nr_supported_ciphers" -ge 38 ]]; then
                    # Likely only PSK/KRB5 ciphers are missing: display discrepancy but no warning
                    if "$HAS_SSL2"; then
                         out ", $nr_supported_ciphers/$nr_sweet32_ciphers (SSLv2: $nr_ssl2_sweet32_ciphers/$nr_ssl2_supported_ciphers) local ciphers"
                         fileout "SWEET32" "OK" "not vulnerable ($nr_supported_ciphers of $nr_sweet32_ciphers (SSLv2: $nr_ssl2_sweet32_ciphers/$nr_ssl2_supported_ciphers)) local ciphers" "$cve" "$cwe"
                    else
                         out ", $nr_supported_ciphers/$nr_sweet32_ciphers local ciphers"
                         fileout "SWEET32" "OK" "not vulnerable ($nr_supported_ciphers of $nr_sweet32_ciphers local ciphers" "$cve" "$cwe"
                    fi
               else
                    if "$HAS_SSL2"; then
                         pr_warning ", $nr_supported_ciphers/$nr_sweet32_ciphers (SSLv2: $nr_ssl2_sweet32_ciphers/$nr_ssl2_supported_ciphers) local ciphers"
                         fileout "SWEET32" "WARN" "not vulnerable but ($nr_supported_ciphers of $nr_sweet32_ciphers (SSLv2: $nr_ssl2_sweet32_ciphers/$nr_ssl2_supported_ciphers)) local ciphers only" "$cve" "$cwe"
                    else
                         pr_warning ", $nr_supported_ciphers/$nr_sweet32_ciphers local ciphers"
                         fileout "SWEET32" "WARN" "not vulnerable but ($nr_supported_ciphers of $nr_sweet32_ciphers) local ciphers only" "$cve" "$cwe"
                    fi
               fi
          fi
     fi
     outln
     tmpfile_handle ${FUNCNAME[0]}.txt
     [[ $sclient_success -ge 6 ]] && return 1
     return 0
}


# Padding Oracle On Downgraded Legacy Encryption, in a nutshell: don't use CBC Ciphers in SSLv3
run_ssl_poodle() {
     local -i sclient_success=0
     local cbc_ciphers="ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:SRP-DSS-AES-256-CBC-SHA:SRP-RSA-AES-256-CBC-SHA:SRP-AES-256-CBC-SHA:DHE-PSK-AES256-CBC-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DH-RSA-AES256-SHA:DH-DSS-AES256-SHA:DHE-RSA-CAMELLIA256-SHA:DHE-DSS-CAMELLIA256-SHA:DH-RSA-CAMELLIA256-SHA:DH-DSS-CAMELLIA256-SHA:AECDH-AES256-SHA:ADH-AES256-SHA:ADH-CAMELLIA256-SHA:ECDH-RSA-AES256-SHA:ECDH-ECDSA-AES256-SHA:AES256-SHA:ECDHE-PSK-AES256-CBC-SHA:CAMELLIA256-SHA:RSA-PSK-AES256-CBC-SHA:PSK-AES256-CBC-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:SRP-DSS-AES-128-CBC-SHA:SRP-RSA-AES-128-CBC-SHA:SRP-AES-128-CBC-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:DH-RSA-AES128-SHA:DH-DSS-AES128-SHA:DHE-RSA-SEED-SHA:DHE-DSS-SEED-SHA:DH-RSA-SEED-SHA:DH-DSS-SEED-SHA:DHE-RSA-CAMELLIA128-SHA:DHE-DSS-CAMELLIA128-SHA:DH-RSA-CAMELLIA128-SHA:DH-DSS-CAMELLIA128-SHA:AECDH-AES128-SHA:ADH-AES128-SHA:ADH-SEED-SHA:ADH-CAMELLIA128-SHA:ECDH-RSA-AES128-SHA:ECDH-ECDSA-AES128-SHA:AES128-SHA:ECDHE-PSK-AES128-CBC-SHA:DHE-PSK-AES128-CBC-SHA:SEED-SHA:CAMELLIA128-SHA:IDEA-CBC-SHA:RSA-PSK-AES128-CBC-SHA:PSK-AES128-CBC-SHA:KRB5-IDEA-CBC-SHA:KRB5-IDEA-CBC-MD5:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:SRP-DSS-3DES-EDE-CBC-SHA:SRP-RSA-3DES-EDE-CBC-SHA:SRP-3DES-EDE-CBC-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DH-RSA-DES-CBC3-SHA:DH-DSS-DES-CBC3-SHA:AECDH-DES-CBC3-SHA:ADH-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:RSA-PSK-3DES-EDE-CBC-SHA:PSK-3DES-EDE-CBC-SHA:KRB5-DES-CBC3-SHA:KRB5-DES-CBC3-MD5:ECDHE-PSK-3DES-EDE-CBC-SHA:DHE-PSK-3DES-EDE-CBC-SHA:EXP1024-DHE-DSS-DES-CBC-SHA:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DH-RSA-DES-CBC-SHA:DH-DSS-DES-CBC-SHA:ADH-DES-CBC-SHA:EXP1024-DES-CBC-SHA:DES-CBC-SHA:KRB5-DES-CBC-SHA:KRB5-DES-CBC-MD5:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-ADH-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-KRB5-RC2-CBC-SHA:EXP-KRB5-DES-CBC-SHA:EXP-KRB5-RC2-CBC-MD5:EXP-KRB5-DES-CBC-MD5:EXP-DH-DSS-DES-CBC-SHA:EXP-DH-RSA-DES-CBC-SHA"
     local cbc_ciphers_hex="c0,14, c0,0a, c0,22, c0,21, c0,20, 00,91, 00,39, 00,38, 00,37, 00,36, 00,88, 00,87, 00,86, 00,85, c0,19, 00,3a, 00,89, c0,0f, c0,05, 00,35, c0,36, 00,84, 00,95, 00,8d, c0,13, c0,09, c0,1f, c0,1e, c0,1d, 00,33, 00,32, 00,31, 00,30, 00,9a, 00,99, 00,98, 00,97, 00,45, 00,44, 00,43, 00,42, c0,18, 00,34, 00,9b, 00,46, c0,0e, c0,04, 00,2f, c0,35, 00,90, 00,96, 00,41, 00,07, 00,94, 00,8c, 00,21, 00,25, c0,12, c0,08, c0,1c, c0,1b, c0,1a, 00,16, 00,13, 00,10, 00,0d, c0,17, 00,1b, c0,0d, c0,03, 00,0a, 00,93, 00,8b, 00,1f, 00,23, c0,34, 00,8f, 00,63, 00,15, 00,12, 00,0f, 00,0c, 00,1a, 00,62, 00,09, 00,1e, 00,22, 00,14, 00,11, 00,19, 00,08, 00,06, 00,27, 00,26, 00,2a, 00,29, 00,0b, 00,0e"
     local hint=""
     local -i nr_cbc_ciphers=0
     local using_sockets=true
     local cve="CVE-2014-3566"
     local cwe="CWE-310"
     local jsonID="POODLE_SSL"

     [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for SSLv3 POODLE (Padding Oracle On Downgraded Legacy Encryption) " && outln
     pr_bold " POODLE, SSL"; out " ($cve)               "

     if "$TLS13_ONLY" || [[ $(has_server_protocol ssl3) -eq 1 ]]; then
          # one condition should normally suffice but we don't know when run_poddle() was called
          pr_svrty_best "not vulnerable (OK)"
          outln ", no SSLv3 support"
          fileout "$jsonID" "OK" "not vulnerable, no SSLv3" "$cve" "$cwe"
          return 0
     fi

     "$SSL_NATIVE" && using_sockets=false
     # The openssl binary distributed has almost everything we need (PSK and KRB5 ciphers are typically missing).
     # Measurements show that there's little impact whether we use sockets or TLS here, so the default is sockets here
     if "$using_sockets"; then
          tls_sockets "00" "$cbc_ciphers_hex, 00,ff"
          sclient_success=$?
     else
          if ! "$HAS_SSL3"; then
               prln_local_problem "Your $OPENSSL doesn't support SSLv3"
               return 1
          fi
          nr_cbc_ciphers=$(count_ciphers $cbc_ciphers)
          nr_supported_ciphers=$(count_ciphers $(actually_supported_osslciphers $cbc_ciphers))
          # SNI not needed as SSLv3 has none:
          $OPENSSL s_client $(s_client_options "-ssl3 $STARTTLS $BUGS -cipher $cbc_ciphers -connect $NODEIP:$PORT $PROXY") >$TMPFILE 2>$ERRFILE </dev/null
          sclient_connect_successful $? $TMPFILE
          sclient_success=$?
          [[ "$DEBUG" -eq 2 ]] && grep -Eq "error|failure" $ERRFILE | grep -Eav "unable to get local|verify error"
     fi
     if [[ $sclient_success -eq 0 ]]; then
          POODLE=0
          pr_svrty_high "VULNERABLE (NOT ok)"; out ", uses SSLv3+CBC (check TLS_FALLBACK_SCSV mitigation below)"
          fileout "$jsonID" "HIGH" "VULNERABLE, uses SSLv3+CBC" "$cve" "$cwe" "$hint"
          set_grade_cap "C" "Vulnerable to POODLE"
     else
          POODLE=1
          pr_svrty_best "not vulnerable (OK)";
          if "$using_sockets"; then
               fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe"
          else
               if [[ "$nr_supported_ciphers" -ge 83 ]]; then
                    # Likely only KRB and PSK cipher are missing: display discrepancy but no warning
                    out ", $nr_supported_ciphers/$nr_cbc_ciphers local ciphers"
               else
                    pr_warning ", $nr_supported_ciphers/$nr_cbc_ciphers local ciphers"
               fi
               fileout "$jsonID" "OK" "not vulnerable ($nr_supported_ciphers of $nr_cbc_ciphers local ciphers" "$cve" "$cwe"
          fi
     fi
     outln
     tmpfile_handle ${FUNCNAME[0]}.txt
     return 0
}

# for appliance which use padding, no fallback needed
run_tls_poodle() {
     local cve="CVE-2014-8730"
     local cwe="CWE-310"
     local jsonID="POODLE_TLS"

     pr_bold " POODLE, TLS"; out " ($cve), experimental "
     #FIXME
     prln_warning "#FIXME"
     fileout "$jsonID" "WARN" "Not yet implemented #FIXME" "$cve" "$cwe"
     # set_grade_cap "F" "Vulnerable to POODLE TLS"

     return 0
}

#FIXME: fileout needs to be patched according to new scheme. Postponed as otherwise merge fails ??
#
# This isn't a vulnerability check per se, but checks for the existence of
# the countermeasure to protect against protocol downgrade attacks.
#
run_tls_fallback_scsv() {
     local -i ret=0
     local high_proto="" low_proto=""
     local p high_proto_str protos_to_try
     local jsonID="fallback_SCSV"

     [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for TLS_FALLBACK_SCSV Protection " && outln
     pr_bold " TLS_FALLBACK_SCSV"; out " (RFC 7507)              "

     # First check we have support for TLS_FALLBACK_SCSV in our local OpenSSL
     if ! "$HAS_FALLBACK_SCSV"; then
          prln_local_problem "$OPENSSL lacks TLS_FALLBACK_SCSV support"
          fileout "$jsonID" "WARN" "$OPENSSL lacks TLS_FALLBACK_SCSV support"
          return 1
     fi

     # First determine the highest protocol that the server supports (not including TLSv1.3).
     if [[ "$OPTIMAL_PROTO" == -ssl2 ]]; then
          prln_svrty_critical "No fallback possible, SSLv2 is the only protocol"
          fileout "$jsonID" "CRITICAL" "SSLv2 is the only protocol"
          return 0
     fi
     for p in tls1_2 tls1_1 tls1 ssl3; do
          [[ $(has_server_protocol "$p") -eq 1 ]] && continue
          if [[ $(has_server_protocol "$p") -eq 0 ]]; then
               high_proto="$p"
               break
          fi
          [[ "$p" == ssl3 ]] && ! "$HAS_SSL3" && continue
          $OPENSSL s_client $(s_client_options "-$p $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null
          if sclient_connect_successful $? $TMPFILE; then
               high_proto="$p"
               break
          fi
     done
     case "$high_proto" in
          "tls1_2")
               high_proto_str="TLS 1.2"
               protos_to_try="tls1_1 tls1 ssl3" ;;
          "tls1_1")
               high_proto_str="TLS 1.1"
               protos_to_try="tls1 ssl3" ;;
          "tls1")
               high_proto_str="TLS 1"
               protos_to_try="ssl3" ;;
          "ssl3")
               prln_svrty_high "No fallback possible, SSLv3 is the only protocol"
               fileout "$jsonID" "HIGH" "only SSLv3 supported"
               return 0
               ;;
          *)   if [[ $(has_server_protocol tls1_3) -eq 0 ]]; then
                    # If the server supports TLS 1.3, and does not support TLS 1.2, TLS 1.1, or TLS 1,
                    # then assume it does not support SSLv3, even if SSLv3 cannot be tested.
                    pr_svrty_good "No fallback possible (OK)"; outln ", TLS 1.3 is the only protocol"
                    fileout "$jsonID" "OK" "only TLS 1.3 supported"
               elif [[ $(has_server_protocol tls1_3) -eq 1 ]] && \
                    ( [[ $(has_server_protocol ssl3) -eq 1 ]] || "$HAS_SSL3" ); then
                    # TLS 1.3, TLS 1.2, TLS 1.1, TLS 1, and SSLv3 are all not supported.
                    # This may be an SSLv2-only server, if $OPENSSL does not support SSLv2.
                    prln_warning "test failed (couldn't connect)"
                    fileout "$jsonID" "WARN" "Check failed. (couldn't connect)"
                    return 1
               elif [[ $(has_server_protocol tls1_3) -eq 1 ]]; then
                    # If the server does not support TLS 1.3, TLS 1.2, TLS 1.1, or TLS 1, and
                    # support for SSLv3 cannot be tested, then treat it as HIGH severity, since
                    # it is very likely that SSLv3 is the only supported protocol.
                    pr_svrty_high "NOT ok, no fallback possible"; outln ", TLS 1.3, 1.2, 1.1 and 1.0 not supported"
                    fileout "$jsonID" "HIGH" "TLS 1.3, 1.2, 1.1, 1.0 not supported"
               else
                    # TLS 1.2, TLS 1.1, and TLS 1 are not supported, but can't tell whether TLS 1.3 is supported.
                    # This could be a TLS 1.3 only server, an SSLv3 only server (if SSLv3 support cannot be tested),
                    # or a server that does not support SSLv3 or any TLS protocol. So, don't report a severity,
                    # since this could either be good or bad.
                    outln "No fallback possible, TLS 1.2, TLS 1.1, and TLS 1 not supported"
                    fileout "$jsonID" "INFO" "TLS 1.2, TLS 1.1, and TLS 1 not supported"
               fi
               return 0
     esac

     # Next find a second protocol that the server supports.
     for p in $protos_to_try; do
          [[ $(has_server_protocol "$p") -eq 1 ]] && continue
          if [[ $(has_server_protocol "$p") -eq 0 ]]; then
               low_proto="$p"
               break
          fi
          [[ "$p" == ssl3 ]] && ! "$HAS_SSL3" && continue
          $OPENSSL s_client $(s_client_options "-$p $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null
          if sclient_connect_successful $? $TMPFILE; then
               low_proto="$p"
               break
          fi
     done

     if ! "$HAS_SSL3" && \
         ( [[ "$low_proto" == ssl3 ]] || \
           ( [[ "$high_proto" == tls1 ]] && [[ $(has_server_protocol ssl3) -eq 2 ]] ) ); then
          # If the protocol that the server would fall back to is SSLv3, but $OPENSSL does
          # not support SSLv3, then the test cannot be performed. So, if $OPENSSL does not
          # support SSLv3 and it is known that SSLv3 is the fallback protocol ($low_proto), then
          # the test cannot be performed. Similarly, if SSLv3 could be the fallback protocol, but
          # support for SSLv3 is unknown, then the test cannot be performed.
          # NOTE: This check assumes that any server that suppports SSLv3 and either TLS 1.2 or
          # TLS 1.1 would also support TLS 1. So, if $high_proto is not TLS 1, then it is assumed
          # that either (1) $low_proto has already been set (to TLS1.1 or TLS 1) or (2) no protocol
          # lower than $high_proto is offered.
          prln_local_problem "Can't test: $OPENSSL does not support SSLv3"
          fileout "$jsonID" "WARN" "Can't test: $OPENSSL does not support SSLv3"
          return 1
     fi
     if [[ -z "$low_proto" ]]; then
          case "$high_proto" in
               "tls1_2")
                    pr_svrty_good "No fallback possible (OK)"; outln ", no protocol below $high_proto_str offered"
                    ;;
               *)   outln "No fallback possible, no protocol below $high_proto_str offered (OK)"
                    ;;
          esac
          fileout "$jsonID" "OK" "no protocol below $high_proto_str offered"
          return 0
     fi
     case "$low_proto" in
          "tls1_1")
               p="-no_tls1_2" ;;
          "tls1")
               p="-no_tls1_2 -no_tls1_1" ;;
          "ssl3")
               p="-no_tls1_2 -no_tls1_1 -no_tls1" ;;
     esac
     "$HAS_TLS13" && p+=" -no_tls1_3"
     debugme echo "Simulating fallback from $high_proto to $low_proto"

     # ...and do the test (we need to parse the error here!)
     $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI $p -fallback_scsv") &>$TMPFILE </dev/null
     if grep -q "CONNECTED(00" "$TMPFILE"; then
          if grep -qa "BEGIN CERTIFICATE" "$TMPFILE"; then
               if [[ -z "$POODLE" ]]; then
                    pr_warning "Rerun including POODLE SSL check. "
                    pr_svrty_medium "Downgrade attack prevention NOT supported"
                    fileout "$jsonID" "WARN" "NOT supported. Pls rerun with POODLE SSL check"
                    ret=1
               elif [[ "$POODLE" -eq 0 ]]; then
                    pr_svrty_high "Downgrade attack prevention NOT supported and vulnerable to POODLE SSL"
                    fileout "$jsonID" "HIGH" "NOT supported and vulnerable to POODLE SSL"
                    set_grade_cap "C" "Vulnerable to POODLE"
               else
                    pr_svrty_medium "Downgrade attack prevention NOT supported"
                    fileout "$jsonID" "MEDIUM" "NOT supported"
               fi
               set_grade_cap "A" "Does not support TLS_FALLBACK_SCSV"

          elif grep -qa "alert inappropriate fallback" "$TMPFILE"; then
               pr_svrty_good "Downgrade attack prevention supported (OK)"
               fileout "$jsonID" "OK" "supported"
          elif grep -qa "alert handshake failure" "$TMPFILE"; then
               pr_svrty_good "Probably OK. "
               fileout "$jsonID" "OK" "Probably oK"
               # see RFC 7507, https://github.com/drwetter/testssl.sh/issues/121
               # other case reported by Nicolas was F5 and at costumer of mine: the same
               pr_svrty_medium "But received non-RFC-compliant \"handshake failure\" instead of \"inappropriate fallback\""
               fileout "$jsonID" "MEDIUM" "received non-RFC-compliant \"handshake failure\" instead of \"inappropriate fallback\""
          elif grep -qa "ssl handshake failure" "$TMPFILE"; then
               pr_svrty_medium "some unexpected \"handshake failure\" instead of \"inappropriate fallback\""
               fileout "$jsonID" "MEDIUM" "some unexpected \"handshake failure\" instead of \"inappropriate fallback\" (likely: warning)"
          else
               pr_warning "Check failed, unexpected result "
               out ", run $PROG_NAME -Z --debug=1 and look at $TEMPDIR/*tls_fallback_scsv.txt"
               fileout "$jsonID" "WARN" "Check failed, unexpected result, run $PROG_NAME -Z --debug=1 and look at $TEMPDIR/*tls_fallback_scsv.txt"
               ret=1
          fi
     else
          pr_warning "test failed (couldn't connect)"
          fileout "$jsonID" "WARN" "Check failed. (couldn't connect)"
          ret=1
     fi

     outln
     tmpfile_handle ${FUNCNAME[0]}.txt
     return $ret
}


# Factoring RSA Export Keys: don't use EXPORT RSA ciphers, see https://freakattack.com/
run_freak() {
     local -i sclient_success=0
     local -i i nr_supported_ciphers=0 len
     # with correct build it should list these 9 ciphers (plus the two latter as SSLv2 ciphers):
     local exportrsa_cipher_list="EXP1024-DES-CBC-SHA:EXP1024-RC2-CBC-MD5:EXP1024-RC4-SHA:EXP1024-RC4-MD5:EXP-EDH-RSA-DES-CBC-SHA:EXP-DH-RSA-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC4-MD5"
     local exportrsa_tls_cipher_list_hex="00,62, 00,61, 00,64, 00,60, 00,14, 00,0E, 00,08, 00,06, 00,03"
     local exportrsa_ssl2_cipher_list_hex="04,00,80, 02,00,80, 00,00,00"
     local detected_ssl2_ciphers
     local addtl_warning="" hexc
     local using_sockets=true
     local cve="CVE-2015-0204"
     local cwe="CWE-310"
     local hint=""
     local jsonID="FREAK"

     [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for FREAK attack " && outln
     pr_bold " FREAK"; out " ($cve)                     "

    if "$TLS13_ONLY"; then
          pr_svrty_best "not vulnerable (OK)"
          [[ $DEBUG -ge 1 ]] && out ", TLS 1.3 only server"
          outln
          fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe"
          return 0
     fi

     "$SSL_NATIVE" && using_sockets=false
     if "$using_sockets"; then
          nr_supported_ciphers=$(count_words "$exportrsa_tls_cipher_list_hex")+$(count_words "$exportrsa_ssl2_cipher_list_hex")
     else
          nr_supported_ciphers=$(count_ciphers $(actually_supported_osslciphers $exportrsa_cipher_list))
     fi
     #echo "========= ${PIPESTATUS[*]}

     case $nr_supported_ciphers in
          0)   prln_local_problem "$OPENSSL doesn't have any EXPORT RSA ciphers configured"
               fileout "$jsonID" "WARN" "Not tested. $OPENSSL doesn't have any EXPORT RSA ciphers configured" "$cve" "$cwe"
               return 0
               ;;
          1|2|3)
               addtl_warning=" ($magenta""tested only with $nr_supported_ciphers out of 9 ciphers only!$off)" ;;
          4|5|6|7)
               addtl_warning=" (tested with $nr_supported_ciphers/9 ciphers)" ;;
          8|9|10|11)
               addtl_warning="" ;;
     esac
     if "$using_sockets"; then
          tls_sockets "03" "$exportrsa_tls_cipher_list_hex, 00,ff"
          sclient_success=$?
          [[ $sclient_success -eq 2 ]] && sclient_success=0
          if [[ $sclient_success -ne 0 ]]; then
               sslv2_sockets "$exportrsa_ssl2_cipher_list_hex" "true"
               if [[ $? -eq 3 ]] && [[ "$V2_HELLO_CIPHERSPEC_LENGTH" -ne 0 ]]; then
                    exportrsa_ssl2_cipher_list_hex="$(strip_spaces "${exportrsa_ssl2_cipher_list_hex//,/}")"
                    len=${#exportrsa_ssl2_cipher_list_hex}
                    detected_ssl2_ciphers="$(grep "Supported cipher: " "$TEMPDIR/$NODEIP.parse_sslv2_serverhello.txt")"
                    for (( i=0; i<len; i+=6 )); do
                         [[ "$detected_ssl2_ciphers" =~ x${exportrsa_ssl2_cipher_list_hex:i:6} ]] && sclient_success=0 && break
                    done
               fi
          fi
     else
          $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -cipher $exportrsa_cipher_list -connect $NODEIP:$PORT $PROXY $SNI -no_ssl2") >$TMPFILE 2>$ERRFILE </dev/null
          sclient_connect_successful $? $TMPFILE
          sclient_success=$?
          debugme grep -Ea "error|failure" $ERRFILE | grep -Eav "unable to get local|verify error"
          if [[ $sclient_success -ne 0 ]] && "$HAS_SSL2"; then
               $OPENSSL s_client $STARTTLS $BUGS -cipher $exportrsa_cipher_list -connect $NODEIP:$PORT $PROXY -ssl2 >$TMPFILE 2>$ERRFILE </dev/null
               sclient_connect_successful $? $TMPFILE
               sclient_success=$?
          fi
     fi
     if [[ $sclient_success -eq 0 ]]; then
          pr_svrty_critical "VULNERABLE (NOT ok)"; out ", uses EXPORT RSA ciphers"
          fileout "$jsonID" "CRITICAL" "VULNERABLE, uses EXPORT RSA ciphers" "$cve" "$cwe" "$hint"
     else
          pr_svrty_best "not vulnerable (OK)"; out "$addtl_warning"
          fileout "$jsonID" "OK" "not vulnerable $addtl_warning" "$cve" "$cwe"
     fi
     outln

     if [[ $DEBUG -ge 2 ]]; then
          if "$using_sockets"; then
               for hexc in $(sed 's/, / /g' <<< "$exportrsa_tls_cipher_list_hex, $exportrsa_ssl2_cipher_list_hex"); do
                    if [[ ${#hexc} -eq 5 ]]; then
                         hexc="0x${hexc:0:2},0x${hexc:3:2}"
                    else
                         hexc="0x${hexc:0:2},0x${hexc:3:2},0x${hexc:6:2}"
                    fi
                    for (( i=0; i < TLS_NR_CIPHERS; i++ )); do
                         [[ "$hexc" == "${TLS_CIPHER_HEXCODE[i]}" ]] && break
                    done
                    [[ $i -eq $TLS_NR_CIPHERS ]] && tm_out "$hexc " || tm_out "${TLS_CIPHER_OSSL_NAME[i]} "
               done
               tmln_out
          else
               actually_supported_osslciphers $exportrsa_cipher_list
          fi
     fi
     debugme echo $nr_supported_ciphers

     tmpfile_handle ${FUNCNAME[0]}.txt
     return 0
}

# ARGs see below
# Sets the global DH_GROUP_OFFERED, start value: "", after this function:
#    DH_GROUP_OFFERED=""
#    DH_GROUP_OFFERED="<name of group>"
#    return:  1: common primes file problem, 2: no pkey support, 0: went w/o error
get_common_prime() {
     local jsonID2="$1"
     local key_bitstring="$2"
     local spaces="$3"
     local dh_p=""
     local -i subret=0
     local common_primes_file="$TESTSSL_INSTALL_DIR/etc/common-primes.txt"
     local -i lineno_matched=0

     "$HAS_PKEY" || return 2
     dh_p="$($OPENSSL pkey -pubin -text -noout 2>>$ERRFILE <<< "$key_bitstring" | awk '/prime:|prime P:/,/generator:|generator G:/' | grep -Ev "prime|generator")"
     dh_p="$(strip_spaces "$(colon_to_spaces "$(newline_to_spaces "$dh_p")")")"
     [[ "${dh_p:0:2}" == "00" ]] && dh_p="${dh_p:2}"
     DH_GROUP_LEN_P="$((4*${#dh_p}))"
     debugme tmln_out "len(dh_p): $DH_GROUP_LEN_P  |  dh_p: $dh_p"
     [[ "$DEBUG" -gt 1 ]] && echo "$dh_p" > $TEMPDIR/dh_p.txt
     if [[ ! -s "$common_primes_file" ]]; then
          prln_local_problem "couldn't read common primes file $common_primes_file"
          out "${spaces}"
          fileout "$jsonID2" "WARN" "couldn't read common primes file $common_primes_file"
          return 1
     else
          dh_p="$(toupper "$dh_p")"
          # In the previous line of the match is bascially the hint we want to echo
          # the most elegant thing to get the previous line [ awk '/regex/ { print x }; { x=$0 }' ] doesn't work with gawk
          lineno_matched=$(grep -n "$dh_p" "$common_primes_file" 2>/dev/null | awk -F':' '{ print $1 }')
          if [[ "$lineno_matched" -ne 0 ]]; then
               DH_GROUP_OFFERED="$(awk "NR == $lineno_matched-1" "$common_primes_file" | awk -F'"' '{ print $2 }')"
               #subret=1     # vulnerable: common prime
          else
               DH_GROUP_OFFERED="Unknown DH group"
               :
               #subret=0     # not vulnerable: no known common prime
          fi
          return 0
     fi
}


# helper function for run_logjam see below
#
out_common_prime() {
     local jsonID2="$1"
     local cve="$2"
     local cwe="$3"

     [[ "$DH_GROUP_OFFERED" == ffdhe* ]] && [[ ! "$DH_GROUP_OFFERED" =~ \  ]] && DH_GROUP_OFFERED="RFC7919/$DH_GROUP_OFFERED"
     if [[ "$DH_GROUP_OFFERED" =~ ffdhe ]] && [[ "$DH_GROUP_OFFERED" =~ \  ]]; then
          out "common primes detected: "; pr_italic "$DH_GROUP_OFFERED"
          fileout "$jsonID2" "INFO" "$DH_GROUP_OFFERED" "$cve" "$cwe"
     # Now (below) size matters -- i.e. the bit size. As this is about a known prime we label it more strict.
     # This needs maybe needs another thought as it could appear inconsistent with run_fs and elsewhere.
     # for now we label the bit size similar in the screen, but distinguish the leading text for logjam before
     elif [[ $DH_GROUP_LEN_P -le 800 ]]; then
          pr_svrty_critical "VULNERABLE (NOT ok):"; out " common prime: "
          fileout "$jsonID2" "CRITICAL" "$DH_GROUP_OFFERED" "$cve" "$cwe"
          pr_dh "$DH_GROUP_OFFERED" $DH_GROUP_LEN_P
     elif [[ $DH_GROUP_LEN_P -le 1024 ]]; then
          # really? Here we assume that 1024-bit common prime for nation states are worth and possible to precompute (TBC)
          # otherwise 1024 are just medium
          pr_svrty_high "VULNERABLE (NOT ok):"; out " common prime: "
          fileout "$jsonID2" "HIGH" "$DH_GROUP_OFFERED" "$cve" "$cwe"
          pr_dh "$DH_GROUP_OFFERED" $DH_GROUP_LEN_P
     elif [[ $DH_GROUP_LEN_P -le 1536 ]]; then
          pr_svrty_low "common prime: "
          fileout "$jsonID2" "LOW" "$DH_GROUP_OFFERED" "$cve" "$cwe"
          pr_dh "$DH_GROUP_OFFERED" $DH_GROUP_LEN_P
     else
          out "common prime with $DH_GROUP_LEN_P bits detected: "
          fileout "$jsonID2" "INFO" "$DH_GROUP_OFFERED" "$cve" "$cwe"
          pr_dh "$DH_GROUP_OFFERED" $DH_GROUP_LEN_P
     fi
}


# see https://weakdh.org/logjam.html
run_logjam() {
     local -i sclient_success=0
     local exportdh_cipher_list="EXP1024-DHE-DSS-DES-CBC-SHA:EXP1024-DHE-DSS-RC4-SHA:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA"
     local exportdh_cipher_list_hex="00,63, 00,65, 00,14, 00,11"
     local all_dh_ciphers="cc,15, 00,b3, 00,91, c0,97, 00,a3, 00,9f, cc,aa, c0,a3, c0,9f, 00,6b, 00,6a, 00,39, 00,38, 00,c4, 00,c3, 00,88, 00,87, 00,a7, 00,6d, 00,3a, 00,c5, 00,89, 00,ab, cc,ad, c0,a7, c0,43, c0,45, c0,47, c0,53, c0,57, c0,5b, c0,67, c0,6d, c0,7d, c0,81, c0,85, c0,91, 00,a2, 00,9e, c0,a2, c0,9e, 00,aa, c0,a6, 00,67, 00,40, 00,33, 00,32, 00,be, 00,bd, 00,9a, 00,99, 00,45, 00,44, 00,a6, 00,6c, 00,34, 00,bf, 00,9b, 00,46, 00,b2, 00,90, c0,96, c0,42, c0,44, c0,46, c0,52, c0,56, c0,5a, c0,66, c0,6c, c0,7c, c0,80, c0,84, c0,90, 00,66, 00,18, 00,8e, 00,16, 00,13, 00,1b, 00,8f, 00,63, 00,15, 00,12, 00,1a, 00,65, 00,14, 00,11, 00,19, 00,17, 00,b5, 00,b4, 00,2d" # 93 ciphers
     local -i i nr_supported_ciphers=0 server_key_exchange_len=0 ephemeral_pub_len=0
     local addtl_warning="" hexc
     local -i ret=0 subret=0
     local server_key_exchange key_bitstring=""
     local spaces="                                           "
     local vuln_exportdh_ciphers=false
     local openssl_no_expdhciphers=false
     local str=""
     local using_sockets=true
     local cve="CVE-2015-4000"
     local cwe="CWE-310"
     local hint=""
     local jsonID="LOGJAM"
     local jsonID2="${jsonID}-common_primes"

     [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for LOGJAM vulnerability " && outln
     pr_bold " LOGJAM"; out " ($cve), experimental      "

     "$SSL_NATIVE" && using_sockets=false
     # Also as the openssl binary distributed has everything we need measurements show that
     # there's no impact whether we use sockets or TLS here, so the default is sockets here
     if ! "$using_sockets"; then
          nr_supported_ciphers=$(count_ciphers $(actually_supported_osslciphers $exportdh_cipher_list))
          debugme echo $nr_supported_ciphers
          case $nr_supported_ciphers in
               0)   prln_local_problem "$OPENSSL doesn't have any DH EXPORT ciphers configured"
                    fileout "$jsonID" "WARN" "Not tested. $OPENSSL doesn't support any DH EXPORT ciphers" "$cve" "$cwe"
                    out "$spaces"
                    openssl_no_expdhciphers=true
                    ;;
               1|2|3) addtl_warning=" ($magenta""tested w/ $nr_supported_ciphers/4 ciphers only!$off)" ;;
               4)   ;;
          esac
     fi

     # test for DH export ciphers first
     if "$using_sockets"; then
          tls_sockets "03" "$exportdh_cipher_list_hex, 00,ff"
          sclient_success=$?
          [[ $sclient_success -eq 2 ]] && sclient_success=0
          [[ $sclient_success -eq 0 ]] && vuln_exportdh_ciphers=true
     elif [[ $nr_supported_ciphers -ne 0 ]]; then
          $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -cipher $exportdh_cipher_list -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null
          sclient_connect_successful $? $TMPFILE
          [[ $? -eq 0 ]] && vuln_exportdh_ciphers=true
          debugme grep -Ea "error|failure" $ERRFILE | grep -Eav "unable to get local|verify error"
     fi

     if [[ $DEBUG -ge 2 ]]; then
          if "$using_sockets"; then
               for hexc in $(sed 's/, / /g' <<< "$exportdh_cipher_list_hex"); do
                    hexc="0x${hexc:0:2},0x${hexc:3:2}"
                    for (( i=0; i < TLS_NR_CIPHERS; i++ )); do
                         [[ "$hexc" == "${TLS_CIPHER_HEXCODE[i]}" ]] && break
                    done
                    [[ $i -eq $TLS_NR_CIPHERS ]] && tm_out "$hexc " || tm_out "${TLS_CIPHER_OSSL_NAME[i]} "
               done
               tmln_out
          else
               echo $(actually_supported_osslciphers $exportdh_cipher_list)
          fi
     fi

     # Try all ciphers that use an ephemeral DH key. If successful, check whether the key uses a weak prime.
     if [[ -n "$DH_GROUP_OFFERED" ]]; then
          if [[ "$DH_GROUP_OFFERED" =~ Unknown ]]; then
               subret=0                 # no common DH key detected
          else
               subret=1                 # known prime/DH key
          fi
     elif "$using_sockets"; then
          tls_sockets "03" "$all_dh_ciphers, 00,ff" "ephemeralkey"
          sclient_success=$?
          if [[ $sclient_success -eq 0 ]] || [[ $sclient_success -eq 2 ]]; then
               cp "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" $TMPFILE
               key_bitstring="$(awk '/-----BEGIN PUBLIC KEY/,/-----END PUBLIC KEY/ { print $0 }' $TMPFILE)"
          fi
     else
          # FIXME: determine # of ciphers supported, 48 only are the shipped binaries
          $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -cipher kEDH -msg -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null
          sclient_connect_successful $? $TMPFILE
          if [[ $? -eq 0 ]] && grep -q ServerKeyExchange $TMPFILE; then
               # Example: '<<< TLS 1.0 Handshake [length 010b], ServerKeyExchange'
               # get line with ServerKeyExchange, cut from the beginning to "length ". cut from the end to ']'
               str="$(awk '/<<< TLS 1.[0-2].*ServerKeyExchange$/' $TMPFILE)"
               if [[ -z "$str" ]] ; then
                    str="$(awk '/<<< SSL [2-3].*ServerKeyExchange$/' $TMPFILE)"
               fi
               str="${str#<*length }"
               str="${str%]*}"
               server_key_exchange_len=$(hex2dec "$str")
               server_key_exchange_len=2+$server_key_exchange_len/16
               server_key_exchange="$(grep -A $server_key_exchange_len ServerKeyExchange $TMPFILE | tail -n +2)"
               server_key_exchange="$(toupper "$(strip_spaces "$(newline_to_spaces "$server_key_exchange")")")"
               server_key_exchange="${server_key_exchange%%[!0-9A-F]*}"
               server_key_exchange_len=${#server_key_exchange}
               [[ $server_key_exchange_len -gt 8 ]] && [[ "${server_key_exchange:0:2}" == "0C" ]] && ephemeral_pub_len=$(hex2dec "${server_key_exchange:2:6}")
               [[ $ephemeral_pub_len -ne 0 ]] && [[ $ephemeral_pub_len -le $server_key_exchange_len ]] && key_bitstring="$(get_dh_ephemeralkey "${server_key_exchange:8}")"
          fi
     fi

     if [[ -n "$key_bitstring" ]]; then
          if [[ -z "$DH_GROUP_OFFERED" ]]; then
               get_common_prime "$jsonID2" "$key_bitstring" "$spaces"
               ret=$?                   # no common primes file would be ret=1 --> we should treat that some place else before
          fi
          if [[ "$DH_GROUP_OFFERED" =~ Unknown ]]; then
               subret=0                 # no common DH key detected
          else
               subret=1                 # known prime/DH key
          fi
     elif [[ -z "$DH_GROUP_OFFERED" ]]; then
          subret=3
     fi

     # Now if we have DH export ciphers we print them out first
     if "$vuln_exportdh_ciphers"; then
          pr_svrty_high "VULNERABLE (NOT ok):"; out " uses DH EXPORT ciphers"
          fileout "$jsonID" "HIGH" "VULNERABLE, uses DH EXPORT ciphers" "$cve" "$cwe" "$hint"
          set_grade_cap "B" "Uses weak DH key exchange parameters (vulnerable to LOGJAM)"
          if [[ $subret -eq 3 ]]; then
               out ", no DH key detected with <= TLS 1.2"
               fileout "$jsonID2" "OK" "no DH key detected with <= TLS 1.2"
          elif [[ $subret -eq 1 ]]; then
               out "\n${spaces}"
               out_common_prime "$jsonID2" "$cve" "$cwe"
          elif [[ $subret -eq 0 ]]; then
               out " no common primes detected"
               fileout "$jsonID2" "INFO" "--" "$cve" "$cwe"
          elif [[ $ret -eq 1 ]]; then
               out "FIXME 1"
          fi
     else
          if [[ $subret -eq 1 ]]; then
               out_common_prime "$jsonID2" "$cve" "$cwe"
               set_grade_cap "A" "Uses known DH key exchange parameters"
               if ! "$openssl_no_expdhciphers"; then
                    outln ","
                    out "${spaces}but no DH EXPORT ciphers${addtl_warning}"
                    fileout "$jsonID" "OK" "not vulnerable, no DH EXPORT ciphers,$addtl_warning" "$cve" "$cwe"
               fi
          elif [[ $subret -eq 3 ]]; then
               pr_svrty_good "not vulnerable (OK):"; out " no DH EXPORT ciphers${addtl_warning}"
               fileout "$jsonID" "OK" "not vulnerable, no DH EXPORT ciphers,$addtl_warning" "$cve" "$cwe"
               out ", no DH key detected with <= TLS 1.2"
               fileout "$jsonID2" "OK" "no DH key with <= TLS 1.2" "$cve" "$cwe"
          elif [[ $subret -eq 0 ]]; then
               pr_svrty_good "not vulnerable (OK):"; out " no DH EXPORT ciphers${addtl_warning}"
               fileout "$jsonID" "OK" "not vulnerable, no DH EXPORT ciphers,$addtl_warning" "$cve" "$cwe"
               # we issue a special warning if there's no common prime but the bit length is too low
               if [[ $DH_GROUP_LEN_P -le 1024 ]]; then
                    out "\n${spaces}But: "
                    pr_dh "$DH_GROUP_OFFERED" $DH_GROUP_LEN_P
                    case $? in
                         1) fileout "$jsonID" "CRITICAL" "no DH EXPORT ciphers, no common prime but $DH_GROUP_OFFERED has only $DH_GROUP_LEN_P bits,  $addtl_warning" "$cve" "$cwe" ;;
                         2) fileout "$jsonID" "HIGH" "no DH EXPORT ciphers, no common prime but $DH_GROUP_OFFERED has only $DH_GROUP_LEN_P bits,  $addtl_warning" "$cve" "$cwe";;
                         3) fileout "$jsonID" "MEDIUM" "no DH EXPORT ciphers, no common prime but $DH_GROUP_OFFERED has only $DH_GROUP_LEN_P bits,  $addtl_warning" "$cve" "$cwe";;
                    esac
               else
                    out ", no common prime detected"
                    fileout "$jsonID2" "OK" "--" "$cve" "$cwe"
               fi
          elif [[ $ret -eq 1 ]]; then
               pr_svrty_good "partly not vulnerable:"; out " no DH EXPORT ciphers${addtl_warning}"
               fileout "$jsonID" "OK" "not vulnerable, no DH EXPORT ciphers,$addtl_warning" "$cve" "$cwe"
          fi
     fi

     outln
     tmpfile_handle ${FUNCNAME[0]}.txt
     return $ret
}

# Decrypting RSA with Obsolete and Weakened eNcryption, more @ https://drownattack.com/
run_drown() {
     local -i nr_ciphers_detected ret=0
     local spaces="                                          "
     local cert_fingerprint_sha2=""
     local cve="CVE-2016-0800 CVE-2016-0703"
     local cwe="CWE-310"
     local hint=""
     local jsonID="DROWN"

     if [[ $VULN_COUNT -le $VULN_THRESHLD ]]; then
          outln
          pr_headlineln " Testing for DROWN vulnerability "
          outln
     fi
# if we want to use OPENSSL: check for < openssl 1.0.2g, openssl 1.0.1s if native openssl
     pr_bold " DROWN"; out " (${cve// /, })      "

     # Any fingerprint that is placed in $RSA_CERT_FINGERPRINT_SHA2 is also added to
     # to $CERT_FINGERPRINT_SHA2, so if $CERT_FINGERPRINT_SHA2 is not empty, but
     # $RSA_CERT_FINGERPRINT_SHA2 is empty, then the server doesn't have an RSA certificate.
     if [[ -z "$CERT_FINGERPRINT_SHA2" ]]; then
          get_host_cert "-cipher aRSA"
          [[ $? -eq 0 ]] && cert_fingerprint_sha2="$($OPENSSL x509 -noout -in $HOSTCERT -fingerprint -sha256 2>>$ERRFILE | sed -e 's/^.*Fingerprint=//' -e 's/://g' )"
     else
          cert_fingerprint_sha2="$RSA_CERT_FINGERPRINT_SHA2"
          cert_fingerprint_sha2=${cert_fingerprint_sha2/SHA256 /}
     fi

     if ( [[ "$STARTTLS_PROTOCOL" =~ ldap ]] || [[ "$STARTTLS_PROTOCOL" =~ irc ]] ); then
          prln_local_problem "STARTTLS/$STARTTLS_PROTOCOL and --ssl-native collide here"
          return 1
     fi

     if [[ $(has_server_protocol ssl2) -ne 1 ]]; then
          sslv2_sockets
     else
          [[ aaa == bbb ]]    # provoke retrurn code=1
     fi

     case $? in
          7) # strange reply, couldn't convert the cipher spec length to a hex number
               pr_fixme "strange v2 reply "
               outln " (rerun with DEBUG >=2)"
               [[ $DEBUG -ge 3 ]] && hexdump -C "$TEMPDIR/$NODEIP.sslv2_sockets.dd" | head -1
               fileout "$jsonID" "WARN" "received a strange SSLv2 reply (rerun with DEBUG>=2)" "$cve" "$cwe"
               ret=1
               ;;
          3)   # vulnerable, [[ -n "$cert_fingerprint_sha2" ]] test is not needed as we should have RSA certificate here
               lines=$(count_lines "$(hexdump -C "$TEMPDIR/$NODEIP.sslv2_sockets.dd" 2>/dev/null)")
               debugme tm_out "  ($lines lines)  "
               add_proto_offered ssl2 yes
               if [[ "$lines" -gt 1 ]]; then
                    nr_ciphers_detected=$((V2_HELLO_CIPHERSPEC_LENGTH / 3))
                    if [[ 0 -eq "$nr_ciphers_detected" ]]; then
                         prln_svrty_high "CVE-2015-3197: SSLv2 supported but couldn't detect a cipher (NOT ok)";
                         fileout "$jsonID" "HIGH" "SSLv2 offered, but could not detect a cipher. Make sure you don't use this certificate elsewhere, see https://censys.io/ipv4?q=$cert_fingerprint_sha2" "$cve CVE-2015-3197" "$cwe" "$hint"
                    else
                         prln_svrty_critical  "VULNERABLE (NOT ok), SSLv2 offered with $nr_ciphers_detected ciphers";
                         fileout "$jsonID" "CRITICAL" "VULNERABLE, SSLv2 offered with $nr_ciphers_detected ciphers. Make sure you don't use this certificate elsewhere, see https://censys.io/ipv4?q=$cert_fingerprint_sha2" "$cve" "$cwe" "$hint"
                         set_grade_cap "F" "Vulnerable to DROWN"
                    fi
                    outln "$spaces Make sure you don't use this certificate elsewhere, see:"
                    out "$spaces "
                    pr_url "https://censys.io/ipv4?q=$cert_fingerprint_sha2"
                    outln
               fi
               ;;
          *)   prln_svrty_best "not vulnerable on this host and port (OK)"
               fileout "$jsonID" "OK" "not vulnerable on this host and port" "$cve" "$cwe"
               if [[ -n "$cert_fingerprint_sha2" ]]; then
                    outln "$spaces make sure you don't use this certificate elsewhere with SSLv2 enabled services"
                    out "$spaces "
                    pr_url "https://censys.io/ipv4?q=$cert_fingerprint_sha2"
                    outln " could help you to find out"
                    fileout "${jsonID}_hint" "INFO" "Make sure you don't use this certificate elsewhere with SSLv2 enabled services, see https://censys.io/ipv4?q=$cert_fingerprint_sha2" "$cve" "$cwe"
               else
                    outln "$spaces no RSA certificate, thus certificate can't be used with SSLv2 elsewhere"
                    fileout "${jsonID}_hint" "INFO" "no RSA certificate, can't be used with SSLv2 elsewhere" "$cve" "$cwe"
               fi
               ;;
     esac

     return $ret
}



# Browser Exploit Against SSL/TLS: don't use CBC Ciphers in SSLv3 TLSv1.0
run_beast(){
     local hexc dash cbc_cipher sslvers auth mac
     local -a ciph hexcode normalized_hexcode kx enc export2
     local proto proto_hex
     local -i i subret nr_ciphers=0 sclient_success=0
     local detected_cbc_ciphers="" ciphers_to_test
     local higher_proto_supported=""
     local vuln_beast=false
     local spaces="                                           "
     local cr=$'\n'
     local first=true
     local continued=false
     local cbc_cipher_list="EXP-RC2-CBC-MD5:IDEA-CBC-SHA:EXP-DES-CBC-SHA:DES-CBC-SHA:DES-CBC3-SHA:EXP-DH-DSS-DES-CBC-SHA:DH-DSS-DES-CBC-SHA:DH-DSS-DES-CBC3-SHA:EXP-DH-RSA-DES-CBC-SHA:DH-RSA-DES-CBC-SHA:DH-RSA-DES-CBC3-SHA:EXP-EDH-DSS-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:EDH-DSS-DES-CBC3-SHA:EXP-EDH-RSA-DES-CBC-SHA:EDH-RSA-DES-CBC-SHA:EDH-RSA-DES-CBC3-SHA:EXP-ADH-DES-CBC-SHA:ADH-DES-CBC-SHA:ADH-DES-CBC3-SHA:KRB5-DES-CBC-SHA:KRB5-DES-CBC3-SHA:KRB5-IDEA-CBC-SHA:KRB5-DES-CBC-MD5:KRB5-DES-CBC3-MD5:KRB5-IDEA-CBC-MD5:EXP-KRB5-DES-CBC-SHA:EXP-KRB5-RC2-CBC-SHA:EXP-KRB5-DES-CBC-MD5:EXP-KRB5-RC2-CBC-MD5:AES128-SHA:DH-DSS-AES128-SHA:DH-RSA-AES128-SHA:DHE-DSS-AES128-SHA:DHE-RSA-AES128-SHA:ADH-AES128-SHA:AES256-SHA:DH-DSS-AES256-SHA:DH-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ADH-AES256-SHA:CAMELLIA128-SHA:DH-DSS-CAMELLIA128-SHA:DH-RSA-CAMELLIA128-SHA:DHE-DSS-CAMELLIA128-SHA:DHE-RSA-CAMELLIA128-SHA:ADH-CAMELLIA128-SHA:EXP1024-RC2-CBC-MD5:EXP1024-DES-CBC-SHA:EXP1024-DHE-DSS-DES-CBC-SHA:CAMELLIA256-SHA:DH-DSS-CAMELLIA256-SHA:DH-RSA-CAMELLIA256-SHA:DHE-DSS-CAMELLIA256-SHA:DHE-RSA-CAMELLIA256-SHA:ADH-CAMELLIA256-SHA:PSK-3DES-EDE-CBC-SHA:PSK-AES128-CBC-SHA:PSK-AES256-CBC-SHA:DHE-PSK-3DES-EDE-CBC-SHA:DHE-PSK-AES128-CBC-SHA:DHE-PSK-AES256-CBC-SHA:RSA-PSK-3DES-EDE-CBC-SHA:RSA-PSK-AES128-CBC-SHA:RSA-PSK-AES256-CBC-SHA:SEED-SHA:DH-DSS-SEED-SHA:DH-RSA-SEED-SHA:DHE-DSS-SEED-SHA:DHE-RSA-SEED-SHA:ADH-SEED-SHA:PSK-AES128-CBC-SHA256:PSK-AES256-CBC-SHA384:DHE-PSK-AES128-CBC-SHA256:DHE-PSK-AES256-CBC-SHA384:RSA-PSK-AES128-CBC-SHA256:RSA-PSK-AES256-CBC-SHA384:ECDH-ECDSA-DES-CBC3-SHA:ECDH-ECDSA-AES128-SHA:ECDH-ECDSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-RSA-AES128-SHA:ECDH-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:AECDH-DES-CBC3-SHA:AECDH-AES128-SHA:AECDH-AES256-SHA:SRP-3DES-EDE-CBC-SHA:SRP-RSA-3DES-EDE-CBC-SHA:SRP-DSS-3DES-EDE-CBC-SHA:SRP-AES-128-CBC-SHA:SRP-RSA-AES-128-CBC-SHA:SRP-DSS-AES-128-CBC-SHA:SRP-AES-256-CBC-SHA:SRP-RSA-AES-256-CBC-SHA:SRP-DSS-AES-256-CBC-SHA:ECDHE-PSK-3DES-EDE-CBC-SHA:ECDHE-PSK-AES128-CBC-SHA:ECDHE-PSK-AES256-CBC-SHA:ECDHE-PSK-AES128-CBC-SHA256:ECDHE-PSK-AES256-CBC-SHA384:PSK-CAMELLIA128-SHA256:PSK-CAMELLIA256-SHA384:DHE-PSK-CAMELLIA128-SHA256:DHE-PSK-CAMELLIA256-SHA384:RSA-PSK-CAMELLIA128-SHA256:RSA-PSK-CAMELLIA256-SHA384:ECDHE-PSK-CAMELLIA128-SHA256:ECDHE-PSK-CAMELLIA256-SHA384"
     local cbc_ciphers_hex="00,06, 00,07, 00,08, 00,09, 00,0A, 00,0B, 00,0C, 00,0D, 00,0E, 00,0F, 00,10, 00,11, 00,12, 00,13, 00,14, 00,15, 00,16, 00,19, 00,1A, 00,1B, 00,1E, 00,1F, 00,21, 00,22, 00,23, 00,25, 00,26, 00,27, 00,29, 00,2A, 00,2F, 00,30, 00,31, 00,32, 00,33, 00,34, 00,35, 00,36, 00,37, 00,38, 00,39, 00,3A, 00,41, 00,42, 00,43, 00,44, 00,45, 00,46, 00,61, 00,62, 00,63, 00,84, 00,85, 00,86, 00,87, 00,88, 00,89, 00,8B, 00,8C, 00,8D, 00,8F, 00,90, 00,91, 00,93, 00,94, 00,95, 00,96, 00,97, 00,98, 00,99, 00,9A, 00,9B, 00,AE, 00,AF, 00,B2, 00,B3, 00,B6, 00,B7, C0,03, C0,04, C0,05, C0,08, C0,09, C0,0A, C0,0D, C0,0E, C0,0F, C0,12, C0,13, C0,14, C0,17, C0,18, C0,19, C0,1A, C0,1B, C0,1C, C0,1D, C0,1E, C0,1F, C0,21, C0,22, C0,34, C0,35, C0,36, C0,37, C0,38, C0,64, C0,65, C0,66, C0,67, C0,68, C0,69, C0,70, C0,71, C0,94, C0,95, C0,96, C0,97, C0,98, C0,99, C0,9A, C0,9B, FE,FE, FE,FF, FF,E0, FF,E1"
     local has_dh_bits="$HAS_DH_BITS"
     local using_sockets=true
     local cve="CVE-2011-3389"
     local cwe="CWE-20"
     local hint=""
     local jsonID="BEAST"

     if [[ $VULN_COUNT -le $VULN_THRESHLD ]]; then
          outln
          pr_headlineln " Testing for BEAST vulnerability "
          outln
     fi
     pr_bold " BEAST"; out " ($cve)                     "

     if "$TLS13_ONLY" || ( [[ $(has_server_protocol ssl3) -eq 1 ]] && [[ $(has_server_protocol tls1) -eq 1 ]] ); then
          pr_svrty_good "not vulnerable (OK)"
          outln ", no SSL3 or TLS1"
          fileout "$jsonID" "OK" "not vulnerable, no SSL3 or TLS1" "$cve" "$cwe"
          return 0
     fi

     "$SSL_NATIVE" && using_sockets=false
     # $cbc_ciphers_hex has 126 ciphers, we omitted SRP-AES-256-CBC-SHA bc the trailing 00,ff below will pose
     # a problem for ACE loadbalancers otherwise. So in case we know this is not true, we'll re-add it
     ! "$SERVER_SIZE_LIMIT_BUG" && "$using_sockets" && cbc_ciphers_hex="$cbc_ciphers_hex, C0,20"

     [[ $TLS_NR_CIPHERS == 0 ]] && using_sockets=false
     if "$using_sockets" || [[ $OSSL_VER_MAJOR -lt 1 ]]; then
          for (( i=0; i < TLS_NR_CIPHERS; i++ )); do
               hexc="${TLS_CIPHER_HEXCODE[i]}"
               if [[ ${#hexc} -eq 9 ]] && [[ "${TLS_CIPHER_RFC_NAME[i]}" =~ CBC ]] && \
                  [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA256 ]] && [[ ! "${TLS_CIPHER_RFC_NAME[i]}" =~ SHA384 ]]; then
                    ciph[nr_ciphers]="${TLS_CIPHER_OSSL_NAME[i]}"
                    hexcode[nr_ciphers]="${hexc:2:2},${hexc:7:2}"
                    rfc_ciph[nr_ciphers]="${TLS_CIPHER_RFC_NAME[i]}"
                    kx[nr_ciphers]="${TLS_CIPHER_KX[i]}"
                    enc[nr_ciphers]="${TLS_CIPHER_ENC[i]}"
                    export2[nr_ciphers]="${TLS_CIPHER_EXPORT[i]}"
                    ossl_supported[nr_ciphers]=${TLS_CIPHER_OSSL_SUPPORTED[i]}
                    if "$using_sockets" && "$WIDE" && ! "$has_dh_bits" && \
                       ( [[ ${kx[nr_ciphers]} == Kx=ECDH ]] || [[ ${kx[nr_ciphers]} == Kx=DH ]] || [[ ${kx[nr_ciphers]} == Kx=EDH ]] ); then
                         ossl_supported[nr_ciphers]=false
                    fi
                    if [[ "${hexc:2:2}" == 00 ]]; then
                         normalized_hexcode[nr_ciphers]="x${hexc:7:2}"
                    else
                         normalized_hexcode[nr_ciphers]="x${hexc:2:2}${hexc:7:2}"
                    fi
                    nr_ciphers+=1
               fi
          done
     else
          # no sockets, openssl
          while read hexc dash ciph[nr_ciphers] sslvers kx[nr_ciphers] auth enc[nr_ciphers] mac export2[nr_ciphers]; do
               if [[ ":${cbc_cipher_list}:" =~ :${ciph[nr_ciphers]}: ]]; then
                    ossl_supported[nr_ciphers]=true
                    if [[ "${hexc:2:2}" == "00" ]]; then
                         normalized_hexcode[nr_ciphers]="x${hexc:7:2}"
                    else
                         normalized_hexcode[nr_ciphers]="x${hexc:2:2}${hexc:7:2}"
                    fi
                    nr_ciphers+=1
               fi
          done  < <(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 'ALL' "-tls1 -V")
     fi

     # first determine whether it's mitigated by higher protocols
     for proto in tls1_1 tls1_2; do
          subret=$(has_server_protocol "$proto")
          if [[ $subret -eq 0 ]]; then
               case $proto in
                    tls1_1) higher_proto_supported+=" TLSv1.1" ;;
                    tls1_2) higher_proto_supported+=" TLSv1.2" ;;
               esac
          elif [[ $subret -eq 2 ]]; then
               $OPENSSL s_client $(s_client_options "-state -"${proto}" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") 2>>$ERRFILE >$TMPFILE </dev/null
               if sclient_connect_successful $? $TMPFILE; then
                    higher_proto_supported+=" $(get_protocol $TMPFILE)"
                    add_proto_offered "$proto" yes
               fi
          fi
     done

     for proto in ssl3 tls1; do
          if [[ "$proto" == ssl3 ]] && ! "$using_sockets" && ! locally_supported "-${proto}"; then
               continued=true
               out "                                           "
               continue
          fi
          subret=$(has_server_protocol "$proto")
          if [[ $subret -eq 0 ]]; then
               sclient_success=0
          elif [[ $subret -eq 1 ]]; then
               sclient_success=1
          elif [[ "$proto" != "ssl3" ]] || "$HAS_SSL3"; then
               $OPENSSL s_client $(s_client_options "-"$proto" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE </dev/null
               sclient_connect_successful $? $TMPFILE
               sclient_success=$?
          else
               tls_sockets "00" "$TLS_CIPHER"
               sclient_success=$?
          fi
          if [[ $sclient_success -ne 0 ]]; then                  # protocol supported?
               if "$continued"; then                             # second round: we hit TLS1
                    if "$HAS_SSL3" || "$using_sockets"; then
                         pr_svrty_good "not vulnerable (OK)" ; outln ", no SSL3 or TLS1"
                         fileout "$jsonID" "OK" "not vulnerable, no SSL3 or TLS1" "$cve" "$cwe"
                    else
                         prln_svrty_good "no TLS1 (OK)"
                         fileout "$jsonID" "OK" "not vulnerable, no TLS1" "$cve" "$cwe"
                    fi
                    return 0
               else                # protocol not succeeded but it's the first time
                    continued=true
                    continue       # protocol not supported, so we do not need to check each cipher with that protocol
               fi
          fi # protocol succeeded
          add_proto_offered "$proto" yes

          # now we test in one shot with the precompiled ciphers
          if "$using_sockets"; then
               case "$proto" in
                    "ssl3") proto_hex="00" ;;
                    "tls1") proto_hex="01" ;;
               esac
               tls_sockets "$proto_hex" "$cbc_ciphers_hex, 00,ff"
               [[ $? -eq 0 ]] || continue
          else
               $OPENSSL s_client $(s_client_options "-"$proto" -cipher "$cbc_cipher_list" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE </dev/null
               sclient_connect_successful $? $TMPFILE || continue
          fi

          detected_cbc_ciphers=""
          for ((i=0; i<nr_ciphers; i++)); do
               ciphers_found[i]=false
               sigalg[nr_ciphers]=""
          done
          while true; do
               [[ "$proto" == ssl3 ]] && ! "$HAS_SSL3" && break
               ciphers_to_test=""
               for (( i=0; i < nr_ciphers; i++ )); do
                    ! "${ciphers_found[i]}" && "${ossl_supported[i]}" && ciphers_to_test+=":${ciph[i]}"
               done
               [[ -z "$ciphers_to_test" ]] && break
               $OPENSSL s_client $(s_client_options "-cipher "${ciphers_to_test:1}" -"${proto}" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>>$ERRFILE </dev/null
               sclient_connect_successful $? $TMPFILE || break
               cbc_cipher=$(get_cipher $TMPFILE)
               [[ -z "$cbc_cipher" ]] && break
               for (( i=0; i < nr_ciphers; i++ )); do
                    [[ "$cbc_cipher" == "${ciph[i]}" ]] && break
               done
               ciphers_found[i]=true
               if [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]] || [[ "${rfc_ciph[i]}" == - ]]; then
                    detected_cbc_ciphers+="${ciph[i]} "
               else
                    detected_cbc_ciphers+="${rfc_ciph[i]} "
               fi
               vuln_beast=true
               if "$WIDE" && ( [[ ${kx[i]} == Kx=ECDH ]] || [[ ${kx[i]} == Kx=DH ]] || [[ ${kx[i]} == Kx=EDH ]] ); then
                    dhlen=$(read_dhbits_from_file "$TMPFILE" quiet)
                    kx[i]="${kx[i]} $dhlen"
               fi
               "$WIDE" && "$SHOW_SIGALGO" && grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TMPFILE && \
                    sigalg[i]="$(read_sigalg_from_file "$TMPFILE")"
          done
          if "$using_sockets"; then
               while true; do
                    ciphers_to_test=""
                    for (( i=0; i < nr_ciphers; i++ )); do
                         ! "${ciphers_found[i]}" && ciphers_to_test+=", ${hexcode[i]}"
                    done
                    [[ -z "$ciphers_to_test" ]] && break
                    if "$SHOW_SIGALGO"; then
                         tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "all"
                    else
                         tls_sockets "$proto_hex" "${ciphers_to_test:2}, 00,ff" "ephemeralkey"
                    fi
                    [[ $? -ne 0 ]] && break
                    cbc_cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")
                    for (( i=0; i < nr_ciphers; i++ )); do
                         [[ "$cbc_cipher" == "${rfc_ciph[i]}" ]] && break
                    done
                    [[ $i -eq $nr_ciphers ]] && break
                    ciphers_found[i]=true
                    if ( [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]] && [[ "${ciph[i]}" != - ]] ) || [[ "${rfc_ciph[i]}" == - ]]; then
                         detected_cbc_ciphers+="${ciph[i]} "
                    else
                         detected_cbc_ciphers+="${rfc_ciph[i]} "
                    fi
                    vuln_beast=true
                    if "$WIDE" && ( [[ ${kx[i]} == Kx=ECDH ]] || [[ ${kx[i]} == Kx=DH ]] || [[ ${kx[i]} == Kx=EDH ]] ); then
                         dhlen=$(read_dhbits_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" quiet)
                         kx[i]="${kx[i]} $dhlen"
                    fi
                    "$WIDE" && "$SHOW_SIGALGO" && [[ -r "$HOSTCERT" ]] && \
                         sigalg[i]="$(read_sigalg_from_file "$HOSTCERT")"
               done
          fi

          if "$WIDE" && [[ -n "$detected_cbc_ciphers" ]]; then
               out "\n "; pr_underline "$(toupper $proto):\n";
               if "$first"; then
                    neat_header
               fi
               first=false
               for (( i=0; i < nr_ciphers; i++ )); do
                    if "${ciphers_found[i]}" || "$SHOW_EACH_C"; then
                         neat_list "$(tolower "${normalized_hexcode[i]}")" "${ciph[i]}" "${kx[i]}" "${enc[i]}" "${export2[i]}" "${ciphers_found[i]}"
                         if "$SHOW_EACH_C"; then
                              if "${ciphers_found[i]}"; then
                                   if [[ -n "$higher_proto_supported" ]]; then
                                        pr_svrty_low "available"
                                   else
                                        pr_svrty_medium "available"
                                   fi
                              else
                                   pr_deemphasize "not a/v"
                              fi
                         fi
                         outln "${sigalg[i]}"
                    fi
               done
          fi

          if ! "$WIDE"; then
               if [[ -n "$detected_cbc_ciphers" ]]; then
                    fileout "${jsonID}_CBC_$(toupper $proto)" "MEDIUM" "$detected_cbc_ciphers" "$cve" "$cwe" "$hint"
                    ! "$first" && out "$spaces"
                    out "$(toupper $proto): "
                    [[ -n "$higher_proto_supported" ]] && \
                         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
               else
                    [[ $proto == tls1 ]] && ! $first && echo -n "$spaces "
                    prln_svrty_good "no CBC ciphers for $(toupper $proto) (OK)"
                    first=false
               fi
          else
               if ! "$vuln_beast" ; then
                    prln_svrty_good "no CBC ciphers for $(toupper $proto) (OK)"
                    fileout "${jsonID}_CBC_$(toupper $proto)" "OK" "No CBC ciphers for $(toupper $proto)" "$cve" "$cwe"
               fi
          fi
     done  # for proto in ssl3 tls1

     if "$vuln_beast"; then
          if [[ -n "$higher_proto_supported" ]]; then
               if "$WIDE"; then
                    outln; out " "
                    # NOT ok seems too harsh for me if we have TLS >1.0
                    pr_svrty_low "VULNERABLE"
                    outln " -- but also supports higher protocols (possible mitigation) $higher_proto_supported"
                    outln
               else
                    out "$spaces"
                    pr_svrty_low "VULNERABLE"
                    outln " -- but also supports higher protocols $higher_proto_supported (likely mitigated)"
               fi
               fileout "$jsonID" "LOW" "VULNERABLE -- but also supports higher protocols $higher_proto_supported (likely mitigated)" "$cve" "$cwe" "$hint"
          else
               if "$WIDE"; then
                    outln
               else
                    out "$spaces"
               fi
               pr_svrty_medium "VULNERABLE"
               outln " -- and no higher protocols as mitigation supported"
               fileout "$jsonID" "MEDIUM" "VULNERABLE -- and no higher protocols as mitigation supported" "$cve" "$cwe" "$hint"
               set_grade_cap "B" "Vulnerable to BEAST"
          fi
     fi
     "$first" && ! "$vuln_beast" && prln_svrty_good "no CBC ciphers found for any protocol (OK)"

     "$using_sockets" && HAS_DH_BITS="$has_dh_bits"
     tmpfile_handle ${FUNCNAME[0]}.txt
     return 0
}


# https://web.archive.org/web/20200324101422/http://www.isg.rhul.ac.uk/tls/Lucky13.html
# Paper: https://doi.org/10.1109/SP.2013.42
# in a nutshell: don't offer CBC suites (again). MAC as a fix for padding oracles is not enough. Best: TLS v1.2+ AES GCM
run_lucky13() {
     local spaces="                                           "
     local cbc_ciphers="ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:SRP-DSS-AES-256-CBC-SHA:SRP-RSA-AES-256-CBC-SHA:SRP-AES-256-CBC-SHA:RSA-PSK-AES256-CBC-SHA384:DHE-PSK-AES256-CBC-SHA384:DHE-PSK-AES256-CBC-SHA:ECDHE-PSK-CAMELLIA256-SHA384:RSA-PSK-CAMELLIA256-SHA384:DHE-PSK-CAMELLIA256-SHA384:PSK-AES256-CBC-SHA384:PSK-CAMELLIA256-SHA384:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DH-RSA-AES256-SHA256:DH-DSS-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DH-RSA-AES256-SHA:DH-DSS-AES256-SHA:ECDHE-RSA-CAMELLIA256-SHA384:ECDHE-ECDSA-CAMELLIA256-SHA384:DHE-RSA-CAMELLIA256-SHA256:DHE-DSS-CAMELLIA256-SHA256:DH-RSA-CAMELLIA256-SHA256:DH-DSS-CAMELLIA256-SHA256:DHE-RSA-CAMELLIA256-SHA:DHE-DSS-CAMELLIA256-SHA:DH-RSA-CAMELLIA256-SHA:DH-DSS-CAMELLIA256-SHA:AECDH-AES256-SHA:ADH-AES256-SHA256:ADH-AES256-SHA:ADH-CAMELLIA256-SHA256:ADH-CAMELLIA256-SHA:ECDH-RSA-AES256-SHA384:ECDH-ECDSA-AES256-SHA384:ECDH-RSA-AES256-SHA:ECDH-ECDSA-AES256-SHA:ECDH-RSA-CAMELLIA256-SHA384:ECDH-ECDSA-CAMELLIA256-SHA384:AES256-SHA256:AES256-SHA:CAMELLIA256-SHA256:ECDHE-PSK-AES256-CBC-SHA384:ECDHE-PSK-AES256-CBC-SHA:CAMELLIA256-SHA:RSA-PSK-AES256-CBC-SHA:PSK-AES256-CBC-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:SRP-DSS-AES-128-CBC-SHA:SRP-RSA-AES-128-CBC-SHA:SRP-AES-128-CBC-SHA:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DH-RSA-AES128-SHA256:DH-DSS-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:DH-RSA-AES128-SHA:DH-DSS-AES128-SHA:ECDHE-RSA-CAMELLIA128-SHA256:ECDHE-ECDSA-CAMELLIA128-SHA256:DHE-RSA-CAMELLIA128-SHA256:DHE-DSS-CAMELLIA128-SHA256:DH-RSA-CAMELLIA128-SHA256:DH-DSS-CAMELLIA128-SHA256:DHE-RSA-SEED-SHA:DHE-DSS-SEED-SHA:DH-RSA-SEED-SHA:DH-DSS-SEED-SHA:DHE-RSA-CAMELLIA128-SHA:DHE-DSS-CAMELLIA128-SHA:DH-RSA-CAMELLIA128-SHA:DH-DSS-CAMELLIA128-SHA:AECDH-AES128-SHA:ADH-AES128-SHA256:ADH-AES128-SHA:ADH-CAMELLIA128-SHA256:ADH-SEED-SHA:ADH-CAMELLIA128-SHA:ECDH-RSA-AES128-SHA256:ECDH-ECDSA-AES128-SHA256:ECDH-RSA-AES128-SHA:ECDH-ECDSA-AES128-SHA:ECDH-RSA-CAMELLIA128-SHA256:ECDH-ECDSA-CAMELLIA128-SHA256:AES128-SHA256:AES128-SHA:CAMELLIA128-SHA256:ECDHE-PSK-AES128-CBC-SHA256:ECDHE-PSK-AES128-CBC-SHA:RSA-PSK-AES128-CBC-SHA256:DHE-PSK-AES128-CBC-SHA256:DHE-PSK-AES128-CBC-SHA:SEED-SHA:CAMELLIA128-SHA:ECDHE-PSK-CAMELLIA128-SHA256:RSA-PSK-CAMELLIA128-SHA256:DHE-PSK-CAMELLIA128-SHA256:PSK-AES128-CBC-SHA256:PSK-CAMELLIA128-SHA256:IDEA-CBC-SHA:RSA-PSK-AES128-CBC-SHA:PSK-AES128-CBC-SHA:KRB5-IDEA-CBC-SHA:KRB5-IDEA-CBC-MD5:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:SRP-DSS-3DES-EDE-CBC-SHA:SRP-RSA-3DES-EDE-CBC-SHA:SRP-3DES-EDE-CBC-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DH-RSA-DES-CBC3-SHA:DH-DSS-DES-CBC3-SHA:AECDH-DES-CBC3-SHA:ADH-DES-CBC3-SHA:ECDH-RSA-DES-CBC3-SHA:ECDH-ECDSA-DES-CBC3-SHA:DES-CBC3-SHA:RSA-PSK-3DES-EDE-CBC-SHA:PSK-3DES-EDE-CBC-SHA:KRB5-DES-CBC3-SHA:KRB5-DES-CBC3-MD5:ECDHE-PSK-3DES-EDE-CBC-SHA:DHE-PSK-3DES-EDE-CBC-SHA:EXP1024-DHE-DSS-DES-CBC-SHA:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DH-RSA-DES-CBC-SHA:DH-DSS-DES-CBC-SHA:ADH-DES-CBC-SHA:EXP1024-DES-CBC-SHA:DES-CBC-SHA:KRB5-DES-CBC-SHA:KRB5-DES-CBC-MD5:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-ADH-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-KRB5-RC2-CBC-SHA:EXP-KRB5-DES-CBC-SHA:EXP-KRB5-RC2-CBC-MD5:EXP-KRB5-DES-CBC-MD5:EXP-DH-DSS-DES-CBC-SHA:EXP-DH-RSA-DES-CBC-SHA"
     cbc_ciphers_hex1="c0,28, c0,24, c0,14, c0,0a, c0,22, c0,21, c0,20, 00,b7, 00,b3, 00,91, c0,9b, c0,99, c0,97, 00,af, c0,95, 00,6b, 00,6a, 00,69, 00,68, 00,39, 00,38, 00,37, 00,36, c0,77, c0,73, 00,c4, 00,c3, 00,c2, 00,c1, 00,88, 00,87, 00,86, 00,85, c0,19, 00,6d, 00,3a, 00,c5, 00,89, c0,2a, c0,26, c0,0f, c0,05, c0,79, c0,75, 00,3d, 00,35, 00,c0, c0,38, c0,36, 00,84, 00,95, 00,8d, c0,3d, c0,3f, c0,41, c0,43, c0,45, c0,47, c0,49, c0,4b, c0,4d, c0,4f, c0,65, c0,67, c0,69, c0,71, c0,27, c0,23, c0,13, c0,09, c0,1f, c0,1e, c0,1d, 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,18, 00,6c, 00,34, 00,bf, 00,9b, 00,46, c0,29, c0,25, c0,0e, c0,04, c0,78, c0,74, 00,3c, 00,2f, 00,ba"
     cbc_ciphers_hex2="c0,37, c0,35, 00,b6, 00,b2, 00,90, 00,96, 00,41, c0,9a, c0,98, c0,96, 00,ae, c0,94, 00,07, 00,94, 00,8c, 00,21, 00,25, c0,3c, c0,3e, c0,40, c0,42, c0,44, c0,46, c0,48, c0,4a, c0,4c, c0,4e, c0,64, c0,66, c0,68, c0,70, c0,12, c0,08, c0,1c, c0,1b, c0,1a, 00,16, 00,13, 00,10, 00,0d, c0,17, 00,1b, c0,0d, c0,03, 00,0a, 00,93, 00,8b, 00,1f, 00,23, c0,34, 00,8f, fe,ff, ff,e0, 00,63, 00,15, 00,12, 00,0f, 00,0c, 00,1a, 00,62, 00,09, 00,61, 00,1e, 00,22, fe,fe, ff,e1, 00,14, 00,11, 00,19, 00,08, 00,06, 00,27, 00,26, 00,2a, 00,29, 00,0b, 00,0e"
     local has_dh_bits="$HAS_DH_BITS"
     local -i nr_supported_ciphers=0 sclient_success
     local using_sockets=true
     local cve="CVE-2013-0169"
     local cwe="CWE-310"
     local hint=""
     local jsonID="LUCKY13"

     if [[ $VULN_COUNT -le $VULN_THRESHLD ]]; then
          outln
          pr_headlineln " Testing for LUCKY13 vulnerability "
          outln
     fi
     pr_bold " LUCKY13"; out " ($cve), experimental     "

     if "$TLS13_ONLY"; then
          pr_svrty_best "not vulnerable (OK)"
          [[ $DEBUG -ge 1 ]] && out ", no CBC ciphers in TLS 1.3 only servers"
          outln
          fileout "$jsonID" "OK" "not vulnerable, TLS 1.3 only" "$cve" "$cwe"
          return 0
     fi

     "$SSL_NATIVE" && using_sockets=false
     # The openssl binary distributed has almost everything we need (PSK, KRB5 ciphers and feff, ffe0 are typically missing).
     # Measurements show that there's little impact whether we use sockets or TLS here, so the default is sockets here

     if "$using_sockets"; then
          tls_sockets "03" "${cbc_ciphers_hex1}, 00,ff"
          sclient_success=$?
          [[ "$sclient_success" -eq 2 ]] && sclient_success=0
          if [[ $sclient_success -ne 0 ]]; then
               tls_sockets "03" "${cbc_ciphers_hex2}, 00,ff"
               sclient_success=$?
               [[ $sclient_success -eq 2 ]] && sclient_success=0
          fi
     else
          nr_cbc_ciphers=$(count_ciphers $cbc_ciphers)
          nr_supported_ciphers=$(count_ciphers $(actually_supported_osslciphers $cbc_ciphers))
          $OPENSSL s_client $(s_client_options "$STARTTLS $BUGS -no_ssl2 -cipher $cbc_ciphers -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null
          sclient_connect_successful $? $TMPFILE
          sclient_success=$?
          [[ "$DEBUG" -eq 2 ]] && grep -Eq "error|failure" $ERRFILE | grep -Eav "unable to get local|verify error"
     fi
     if [[ $sclient_success -eq 0 ]]; then
          out "potentially "
          pr_svrty_low "VULNERABLE"; out ", uses cipher block chaining (CBC) ciphers with TLS. Check patches"
          fileout "$jsonID" "LOW" "Potentially vulnerable since use of TLS CBC ciphers was detected.\nRecommendation:\nYou can prevent the LUCKY13 attack by enabling Transport Layer Security configuration in Apache and Nginx Ref: https://docs.veracode.com/r/prevent-ssl-lucky13.\n" "$cve" "$cwe" "$hint"
          # the CBC padding which led to timing differences during MAC processing has been solved in openssl (https://www.openssl.org/news/secadv/20130205.txt)
          # and other software. However we can't tell with reasonable effort from the outside. Thus we still issue a warning and label it experimental
     else
          pr_svrty_best "not vulnerable (OK)";
          if "$using_sockets"; then
               fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe"
          else
               if [[ "$nr_supported_ciphers" -ge 133 ]]; then
                    # Likely only PSK/KRB5 ciphers are missing: display discrepancy but no warning
                    out ", $nr_supported_ciphers/$nr_cbc_ciphers local ciphers"
               else
                    pr_warning ", $nr_supported_ciphers/$nr_cbc_ciphers local ciphers"
               fi
               fileout "$jsonID" "OK" "not vulnerable ($nr_supported_ciphers of $nr_cbc_ciphers local ciphers" "$cve" "$cwe"
          fi
     fi
     outln
     tmpfile_handle ${FUNCNAME[0]}.txt
     [[ $sclient_success -ge 6 ]] && return 1
     return 0
}


# https://tools.ietf.org/html/rfc7465    REQUIRES that TLS clients and servers NEVER negotiate the use of RC4 cipher suites!
# https://en.wikipedia.org/wiki/Transport_Layer_Security#RC4_attacks
# https://blog.cryptographyengineering.com/2013/03/attack-of-week-rc4-is-kind-of-broken-in.html
#
run_rc4() {
     local -i rc4_offered=0
     local -i nr_ciphers=0 nr_ossl_ciphers=0 nr_nonossl_ciphers=0 sclient_success=0
     local n auth mac hexc sslv2_ciphers_hex="" sslv2_ciphers_ossl="" s
     local -a normalized_hexcode hexcode ciph sslvers kx enc export2 sigalg ossl_supported
     local -i i
     local -a ciphers_found ciphers_found2 hexcode2 ciph2 rfc_ciph2
     local -i -a index
     local dhlen available="" ciphers_to_test supported_sslv2_ciphers proto
     local has_dh_bits="$HAS_DH_BITS" rc4_detected=""
     local using_sockets=true
     local cve="CVE-2013-2566 CVE-2015-2808"
     local cwe="CWE-310"
     local hint=""
     local jsonID="RC4"

     "$SSL_NATIVE" && using_sockets=false
     "$FAST" && using_sockets=false
     [[ $TLS_NR_CIPHERS == 0 ]] && using_sockets=false

     if [[ $VULN_COUNT -le $VULN_THRESHLD ]]; then
          outln
          pr_headlineln " Checking for vulnerable RC4 Ciphers "
          outln
     fi
     pr_bold " RC4"; out " (${cve// /, })        "

     if "$TLS13_ONLY"; then
          pr_svrty_best "not vulnerable (OK)"
          [[ $DEBUG -ge 1 ]] && out ", no RC4 support in TLS 1.3 only servers"
          outln
          fileout "$jsonID" "OK" "not vulnerable, TLS 1.3 only" "$cve" "$cwe"
          return 0
     fi

     # get a list of all the cipher suites to test
     if "$using_sockets" || [[ $OSSL_VER_MAJOR -lt 1 ]]; then
          for (( i=0; i < TLS_NR_CIPHERS; i++ )); do
               if [[ "${TLS_CIPHER_RFC_NAME[i]}" =~ RC4 ]] && ( "$using_sockets" || "${TLS_CIPHER_OSSL_SUPPORTED[i]}" ); then
                    hexc="$(tolower "${TLS_CIPHER_HEXCODE[i]}")"
                    ciph[nr_ciphers]="${TLS_CIPHER_OSSL_NAME[i]}"
                    rfc_ciph[nr_ciphers]="${TLS_CIPHER_RFC_NAME[i]}"
                    sslvers[nr_ciphers]="${TLS_CIPHER_SSLVERS[i]}"
                    kx[nr_ciphers]="${TLS_CIPHER_KX[i]}"
                    enc[nr_ciphers]="${TLS_CIPHER_ENC[i]}"
                    export2[nr_ciphers]="${TLS_CIPHER_EXPORT[i]}"
                    ciphers_found[nr_ciphers]=false
                    sigalg[nr_ciphers]=""
                    ossl_supported[nr_ciphers]="${TLS_CIPHER_OSSL_SUPPORTED[i]}"
                    if "$using_sockets" && "$WIDE" && ! "$HAS_DH_BITS" &&
                       ( [[ ${kx[nr_ciphers]} == "Kx=ECDH" ]] || [[ ${kx[nr_ciphers]} == "Kx=DH" ]] || [[ ${kx[nr_ciphers]} == "Kx=EDH" ]] ); then
                         ossl_supported[nr_ciphers]=false
                    fi
                    if [[ ${#hexc} -eq 9 ]]; then
                         hexcode[nr_ciphers]="${hexc:2:2},${hexc:7:2}"
                         if [[ "${hexc:2:2}" == 00 ]]; then
                              normalized_hexcode[nr_ciphers]="x${hexc:7:2}"
                         else
                              normalized_hexcode[nr_ciphers]="x${hexc:2:2}${hexc:7:2}"
                         fi
                    else
                         hexcode[nr_ciphers]="${hexc:2:2},${hexc:7:2},${hexc:12:2}"
                         normalized_hexcode[nr_ciphers]="x${hexc:2:2}${hexc:7:2}${hexc:12:2}"
                         sslv2_ciphers_hex+=", ${hexcode[nr_ciphers]}"
                         sslv2_ciphers_ossl+=":${ciph[nr_ciphers]}"
                    fi
                    nr_ciphers+=1
               fi
          done
     else
          while read hexc n ciph[nr_ciphers] sslvers[nr_ciphers] kx[nr_ciphers] auth enc[nr_ciphers] mac export2[nr_ciphers]; do
               if [[ "${ciph[nr_ciphers]}" =~ RC4 ]]; then
                    ciphers_found[nr_ciphers]=false
                    if [[ ${#hexc} -eq 9 ]]; then
                         if [[ "${hexc:2:2}" == 00 ]]; then
                              normalized_hexcode[nr_ciphers]="$(tolower "x${hexc:7:2}")"
                         else
                              normalized_hexcode[nr_ciphers]="$(tolower "x${hexc:2:2}${hexc:7:2}")"
                         fi
                    else
                         normalized_hexcode[nr_ciphers]="$(tolower "x${hexc:2:2}${hexc:7:2}${hexc:12:2}")"
                         sslv2_ciphers_ossl+=":${ciph[nr_ciphers]}"
                    fi
                    sigalg[nr_ciphers]=""
                    ossl_supported[nr_ciphers]=true
                    nr_ciphers+=1
               fi
          done < <($OPENSSL ciphers $OSSL_CIPHERS_S -V 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>>$ERRFILE)
     fi

     if "$using_sockets" && [[ -n "$sslv2_ciphers_hex" ]]; then
          sslv2_sockets "${sslv2_ciphers_hex:2}" "true"
          if [[ $? -eq 3 ]] && [[ "$V2_HELLO_CIPHERSPEC_LENGTH" -ne 0 ]]; then
               supported_sslv2_ciphers="$(grep "Supported cipher: " "$TEMPDIR/$NODEIP.parse_sslv2_serverhello.txt")"
               "$WIDE" && "$SHOW_SIGALGO" && s="$(read_sigalg_from_file "$HOSTCERT")"
               for (( i=0 ; i<nr_ciphers; i++ )); do
                    if [[ "${sslvers[i]}" == SSLv2 ]] && [[ "$supported_sslv2_ciphers" =~ ${normalized_hexcode[i]} ]]; then
                         ciphers_found[i]=true
                         "$WIDE" && "$SHOW_SIGALGO" && sigalg[i]="$s"
                         rc4_offered=1
                    fi
               done
          fi
     elif "$HAS_SSL2" && [[ -n "$sslv2_ciphers_ossl" ]]; then
          $OPENSSL s_client -cipher "${sslv2_ciphers_ossl:1}" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY -ssl2 >$TMPFILE 2>$ERRFILE </dev/null
          sclient_connect_successful $? "$TMPFILE"
          if [[ $? -eq 0 ]]; then
               supported_sslv2_ciphers="$(grep -A 4 "Ciphers common between both SSL endpoints:" $TMPFILE)"
               "$WIDE" && "$SHOW_SIGALGO" && s="$(read_sigalg_from_file "$TMPFILE")"
               for (( i=0 ; i<nr_ciphers; i++ )); do
                    if [[ "${sslvers[i]}" == SSLv2 ]] && [[ "$supported_sslv2_ciphers" =~ ${ciph[i]} ]]; then
                         ciphers_found[i]=true
                         "$WIDE" && "$SHOW_SIGALGO" && sigalg[i]="$s"
                         rc4_offered=1
                    fi
               done
          fi
     fi

     for (( i=0; i < nr_ciphers; i++ )); do
          if "${ossl_supported[i]}" && [[ "${sslvers[i]}" != SSLv2 ]]; then
               ciphers_found2[nr_ossl_ciphers]=false
               ciph2[nr_ossl_ciphers]="${ciph[i]}"
               index[nr_ossl_ciphers]=$i
               nr_ossl_ciphers+=1
          fi
     done

     for proto in -no_ssl2 -tls1_1 -tls1 -ssl3; do
          [[ "$proto" != -no_ssl2 ]] && [[ $(has_server_protocol "${proto:1}") -eq 1 ]] && continue
          ! "$HAS_SSL3" && [[ "$proto" == -ssl3 ]] && continue
          while true; do
               ciphers_to_test=""
               for (( i=0; i < nr_ossl_ciphers; i++ )); do
                    ! "${ciphers_found2[i]}" && ciphers_to_test+=":${ciph2[i]}"
               done
               [[ -z "$ciphers_to_test" ]] && break
               $OPENSSL s_client $(s_client_options "$proto -cipher "${ciphers_to_test:1}" $STARTTLS $BUGS -connect $NODEIP:$PORT $PROXY $SNI") >$TMPFILE 2>$ERRFILE </dev/null
               sclient_connect_successful $? "$TMPFILE" || break
               cipher=$(get_cipher $TMPFILE)
               [[ -z "$cipher" ]] && break
               for (( i=0; i < nr_ossl_ciphers; i++ )); do
                    [[ "$cipher" == "${ciph2[i]}" ]] && ciphers_found2[i]=true && break
               done
               [[ $i -eq $nr_ossl_ciphers ]] && break
               rc4_offered=1
               i=${index[i]}
               ciphers_found[i]=true
               if "$WIDE" && ( [[ ${kx[i]} == "Kx=ECDH" ]] || [[ ${kx[i]} == "Kx=DH" ]] || [[ ${kx[i]} == "Kx=EDH" ]] ); then
                    dhlen=$(read_dhbits_from_file "$TMPFILE" quiet)
                    kx[i]="${kx[i]} $dhlen"
               fi
               "$WIDE" && "$SHOW_SIGALGO" && grep -q "\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-" $TMPFILE && \
                    sigalg[i]="$(read_sigalg_from_file "$TMPFILE")"

               # If you use RC4 with newer protocols, you are punished harder
               if [[ "$proto" == -tls1_1 ]]; then
                    set_grade_cap "C" "RC4 ciphers offered on TLS 1.1"
               fi
          done
     done

     if "$using_sockets"; then
          for (( i=0; i < nr_ciphers; i++ )); do
               if ! "${ciphers_found[i]}" && [[ "${sslvers[i]}" != SSLv2 ]]; then
                    ciphers_found2[nr_nonossl_ciphers]=false
                    hexcode2[nr_nonossl_ciphers]="${hexcode[i]}"
                    rfc_ciph2[nr_nonossl_ciphers]="${rfc_ciph[i]}"
                    index[nr_nonossl_ciphers]=$i
                    nr_nonossl_ciphers+=1
               fi
          done
     fi

     for proto in 03 02 01 00; do
          [[ $(has_server_protocol "$proto") -eq 1 ]] && continue
          while true; do
               ciphers_to_test=""
               for (( i=0; i < nr_nonossl_ciphers; i++ )); do
                    ! "${ciphers_found2[i]}" && ciphers_to_test+=", ${hexcode2[i]}"
               done
               [[ -z "$ciphers_to_test" ]] && break
               if "$WIDE" && "$SHOW_SIGALGO"; then
                    tls_sockets "$proto" "${ciphers_to_test:2}, 00,ff" "all"
               else
                    tls_sockets "$proto" "${ciphers_to_test:2}, 00,ff" "ephemeralkey"
               fi
               sclient_success=$?
               [[ $sclient_success -ne 0 ]] && [[ $sclient_success -ne 2 ]] && break
               cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")
               for (( i=0; i < nr_nonossl_ciphers; i++ )); do
                    [[ "$cipher" == "${rfc_ciph2[i]}" ]] && ciphers_found2[i]=true && break
               done
               [[ $i -eq $nr_nonossl_ciphers ]] && break
               rc4_offered=1
               i=${index[i]}
               ciphers_found[i]=true
               if "$WIDE" && ( [[ ${kx[i]} == "Kx=ECDH" ]] || [[ ${kx[i]} == "Kx=DH" ]] || [[ ${kx[i]} == "Kx=EDH" ]] ); then
                    dhlen=$(read_dhbits_from_file "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" quiet)
                    kx[i]="${kx[i]} $dhlen"
               fi
               "$WIDE" && "$SHOW_SIGALGO" && [[ -r "$HOSTCERT" ]] && \
                    sigalg[i]="$(read_sigalg_from_file "$HOSTCERT")"
          done
     done

     if [[ $rc4_offered -eq 1 ]]; then
          "$WIDE" || pr_svrty_high "VULNERABLE (NOT ok): "
          if "$WIDE"; then
               outln "\n"
               neat_header
          fi
          for (( i=0 ; i<nr_ciphers; i++ )); do
               if ! "${ciphers_found[i]}" && ! "$SHOW_EACH_C"; then
                    continue                 # no successful connect AND not verbose displaying each cipher
               fi
               if "$WIDE"; then
                    neat_list "${normalized_hexcode[i]}" "${ciph[i]}" "${kx[i]}" "${enc[i]}" "${export2[i]}" "${ciphers_found[i]}"
                    if "$SHOW_EACH_C"; then
                         if "${ciphers_found[i]}"; then
                              pr_svrty_high "available"
                         else
                              pr_deemphasize "not a/v"
                         fi
                    fi
                    outln "${sigalg[i]}"
               fi
               if "${ciphers_found[i]}"; then
                    if ( [[ "$DISPLAY_CIPHERNAMES" =~ openssl ]] && [[ "${ciph[i]}" != "-" ]] ) || [[ "${rfc_ciph[i]}" == "-" ]]; then
                         rc4_detected+="${ciph[i]} "
                    else
                         rc4_detected+="${rfc_ciph[i]} "
                    fi
               fi
          done
          ! "$WIDE" && pr_svrty_high "$(out_row_aligned_max_width "$rc4_detected" "                                                                " $TERM_WIDTH)"
          outln
          "$WIDE" && out " " && prln_svrty_high "VULNERABLE (NOT ok)"
          fileout "$jsonID" "HIGH" "VULNERABLE, Detected ciphers: $rc4_detected" "$cve" "$cwe" "$hint"
          set_grade_cap "B" "RC4 ciphers offered"
     elif [[ $nr_ciphers -eq 0 ]]; then
          prln_local_problem "No RC4 Ciphers configured in $OPENSSL"
          fileout "$jsonID" "WARN" "RC4 ciphers not supported by local OpenSSL ($OPENSSL)"
     else
          prln_svrty_good "no RC4 ciphers detected (OK)"
          fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe"
     fi
     outln

     "$using_sockets" && HAS_DH_BITS="$has_dh_bits"
     tmpfile_handle ${FUNCNAME[0]}.txt
     [[ $sclient_success -ge 6 ]] && return 1
     return 0
}


run_youknowwho() {
     local cve="CVE-2013-2566"
     local cwe="CWE-310"
     # NOT FIXME as there's no code: https://web.archive.org/web/20191008002003/http://www.isg.rhul.ac.uk/tls/index.html
     # Paper and presentation: https://www.usenix.org/conference/usenixsecurity13/technical-sessions/paper/alFardan
     # https://blog.cryptographyengineering.com/2013/03/12/attack-of-week-rc4-is-kind-of-broken-in/
     return 0
     # in a nutshell: don't use RC4, really not!
}

run_tls_truncation() {
     # https://www.usenix.org/conference/woot13/workshop-program/presentation/smyth
     # https://secure-resumption.com/tlsauth.pdf
     #FIXME: difficult to test, is there any test available: pls let me know
     :
}

# Test for various server implementation errors that aren't tested for elsewhere.
# Inspired by https://datatracker.ietf.org/doc/draft-ietf-tls-grease.
run_grease() {
     local -i success
     local bug_found=false
     local normal_hello_ok=false
     local cipher_list proto selected_cipher selected_cipher_hex="" extn rnd_bytes
     local alpn_proto alpn alpn_list_len_hex extn_len_hex
     local selected_alpn_protocol grease_selected_alpn_protocol
     local ciph list temp curve_found
     local -i i j rnd alpn_list_len extn_len debug_level=""
     local -i ret=0
     # Note: The following values were taken from https://datatracker.ietf.org/doc/draft-ietf-tls-grease.
     # These arrays may need to be updated if the values change in the final version of this document.
     local -a -r grease_cipher_suites=( "0a,0a" "1a,1a" "2a,2a" "3a,3a" "4a,4a" "5a,5a" "6a,6a" "7a,7a" "8a,8a" "9a,9a" "aa,aa" "ba,ba" "ca,ca" "da,da" "ea,ea" "fa,fa" )
     local -a -r grease_supported_groups=( "0a,0a" "1a,1a" "2a,2a" "3a,3a" "4a,4a" "5a,5a" "6a,6a" "7a,7a" "8a,8a" "9a,9a" "aa,aa" "ba,ba" "ca,ca" "da,da" "ea,ea" "fa,fa" )
     local -a -r grease_extn_values=( "0a,0a" "1a,1a" "2a,2a" "3a,3a" "4a,4a" "5a,5a" "6a,6a" "7a,7a" "8a,8a" "9a,9a" "aa,aa" "ba,ba" "ca,ca" "da,da" "ea,ea" "fa,fa" )
     local -r ecdhe_ciphers="cc,14, cc,13, c0,30, c0,2c, c0,28, c0,24, c0,14, c0,0a, c0,9b, cc,a9, cc,a8, c0,af, c0,ad, c0,77, c0,73, c0,19, cc,ac, c0,38, c0,36, c0,49, c0,4d, c0,5d, c0,61, c0,71, c0,87, c0,8b, c0,2f, c0,2b, c0,27, c0,23, c0,13, c0,09, c0,ae, c0,ac, c0,76, c0,72, c0,18, c0,37, c0,35, c0,9a, c0,48, c0,4c, c0,5c, c0,60, c0,70, c0,86, c0,8a, c0,11, c0,07, c0,16, c0,33, c0,12, c0,08, c0,17, c0,34, c0,10, c0,06, c0,15, c0,3b, c0,3a, c0,39"
     local jsonID="GREASE"

     outln; pr_headline " Testing for server implementation bugs "; outln "\n"

     # Many of the following checks work by modifying the "basic" call to
     # tls_sockets() and assuming the tested-for bug is present if the
     # connection fails. However, this only works if the connection succeeds
     # with the "basic" call. So, keep trying different "basic" calls until
     # one is found that succeeds.
     for (( i=0; i < 5; i++ )); do
          case $i in
               0) proto="03" ; cipher_list="$TLS12_CIPHER" ;;
               2) proto="02" ; cipher_list="$TLS_CIPHER" ;;
               3) proto="01" ; cipher_list="$TLS_CIPHER" ;;
               4) proto="00" ; cipher_list="$TLS_CIPHER" ;;
          esac
          tls_sockets "$proto" "$cipher_list"
          success=$?
          if [[ $success -eq 0 ]] || [[ $success -eq 2 ]]; then
               break
          fi
     done
     if [[ $success -eq 0 ]] || [[ $success -eq 2 ]]; then
          selected_cipher=$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")
          if [[ $TLS_NR_CIPHERS -ne 0 ]]; then
               for (( i=0; i < TLS_NR_CIPHERS; i++ )); do
                    [[ "$selected_cipher" == "${TLS_CIPHER_RFC_NAME[i]}" ]] && selected_cipher_hex="${TLS_CIPHER_HEXCODE[i]}" && break
               done
          elif "$HAS_SSL2"; then
               selected_cipher_hex="$($OPENSSL ciphers -V -tls1 'ALL:COMPLEMENTOFALL' | awk '/'" $selected_cipher "'/ { print $1 }')"
          elif "$HAS_CIPHERSUITES"; then
               selected_cipher_hex="$($OPENSSL ciphers -V -ciphersuites "$TLS13_OSSL_CIPHERS" 'ALL:COMPLEMENTOFALL'| awk '/'" $selected_cipher "'/ { print $1 }')"
          else
               selected_cipher_hex="$($OPENSSL ciphers -V 'ALL:COMPLEMENTOFALL'| awk '/'" $selected_cipher "'/ { print $1 }')"
          fi
          if [[ -n "$selected_cipher_hex" ]]; then
               normal_hello_ok=true
               selected_cipher_hex="${selected_cipher_hex:2:2},${selected_cipher_hex:7:2}"
          fi
     else
          proto="03"
     fi

     # Test for yaSSL bug - server only looks at second byte of each cipher
     # suite listed in ClientHello (see issue #793). First check to see if
     # server ignores the ciphers in the ClientHello entirely, then check to
     # see if server only looks at second byte of each offered cipher.

     # Send a list of non-existent ciphers where the second byte does not match
     # any existing cipher.

     # Need to ensure that $TEMPDIR/$NODEIP.parse_tls_serverhello.txt contains the results of the
     # most recent calls to tls_sockets even if tls_sockets is not successful. Setting $DEBUG to
     # a non-zero value ensures this. Setting it to 1 prevents any extra information from being
     # displayed.
     debug_level="$DEBUG"
     [[ $DEBUG -eq 0 ]] && DEBUG=1
     debugme echo -e "\nSending ClientHello with non-existent ciphers."
     tls_sockets "$proto" "de,d0, de,d1, d3,d2, de,d3, 00,ff"
     success=$?
     if [[ $success -eq 0 ]] || [[ $success -eq 2 ]]; then
          prln_svrty_medium " Server claims to support non-existent cipher suite."
          fileout "$jsonID" "MEDIUM" "Server claims to support non-existent cipher suite."
          bug_found=true
     elif grep -q "The ServerHello specifies a cipher suite that wasn't included in the ClientHello" "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" ; then
          prln_svrty_medium " Server responded with a ServerHello rather than an alert even though it doesn't support any of the client-offered cipher suites."
          fileout "$jsonID" "MEDIUM" "Server responded with a ServerHello rather than an alert even though it doesn't support any of the client-offered cipher suites."
          bug_found=true
     else
          # Send a list of non-existent ciphers such that for each cipher that
          # is defined, there is one in the list that matches in the second byte
          # (but make sure list contains at more 127 ciphers).
          debugme echo -e "\nSending ClientHello with non-existent ciphers, but that match existing ciphers in second byte."
          tls_sockets "$proto" "de,01, de,02, de,03, de,04, de,05, de,06, de,07, de,08, de,09, de,0a, de,0b, de,0c, de,0d, de,0e, de,0f, de,10, de,11, de,12, de,13, de,14, de,15, de,16, de,17, de,18, de,19, de,1a, de,1b, de,23, de,24, de,25, de,26, de,27, de,28, de,29, de,2a, de,2b, de,2c, de,2d, de,2e, de,2f, de,30, de,31, de,32, de,33, de,34, de,35, de,36, de,37, de,38, de,39, de,3a, de,3b, de,3c, de,3d, de,3e, de,3f, de,40, de,41, de,42, de,43, de,44, de,45, de,46, de,60, de,61, de,62, de,63, de,64, de,65, de,66, de,67, de,68, de,69, de,6a, de,6b, de,6c, de,6d, de,72, de,73, de,74, de,75, de,76, de,77, de,78, de,79, de,84, de,85, de,86, de,87, de,88, de,89, de,96, de,97, de,98, de,99, de,9a, de,9b, de,9c, de,9d, de,9e, de,9f, de,a0, de,a1, de,a2, de,a3, de,a4, de,a5, de,a6, de,a7, de,ba, de,bb, de,bc, de,bd, de,be, de,bf, de,c0, de,c1, de,c2, de,c3, de,c4, de,c5, 00,ff"
          success=$?
          if [[ $success -eq 0 ]] || [[ $success -eq 2 ]]; then
               prln_svrty_medium " Server claims to support non-existent cipher suite."
               fileout "$jsonID" "MEDIUM" "Server claims to support non-existent cipher suite."
               bug_found=true
          elif grep -q " The ServerHello specifies a cipher suite that wasn't included in the ClientHello" "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" ; then
               prln_svrty_medium " Server only compares against second byte in each cipher suite in ClientHello."
               fileout "$jsonID" "MEDIUM" "Server only compares against second byte in each cipher suite in ClientHello."
               bug_found=true
          fi
     fi
     DEBUG="$debug_level"

     # Check that server ignores unrecognized extensions
     # see https://datatracker.ietf.org/doc/draft-ietf-tls-grease
     if "$normal_hello_ok" && [[ "$proto" != "00" ]]; then
          # Try multiple different randomly-generated GREASE extensions,
          # but make final test use zero-length extension value, just to
          # be sure that works before testing server with a zero-length
          # extension as the final extension.
          for (( i=1; i <= 5; i++ )); do
               # Create a random extension using one of the GREASE values.
               rnd=$RANDOM%${#grease_extn_values[@]}
               extn="${grease_extn_values[rnd]}"
               if [[ $i -eq 5 ]]; then
                    extn_len=0
               else
                    # Not sure what a good upper bound is here, but a key_share
                    # extension with an ffdhe8192 would be over 1024 bytes.
                    extn_len=$RANDOM%1024
               fi
               extn_len_hex=$(printf "%04x" $extn_len)
               extn+=",${extn_len_hex:0:2},${extn_len_hex:2:2}"
               for (( j=0; j <= extn_len-2; j+=2 )); do
                    rnd_bytes="$(printf "%04x" $RANDOM)"
                    extn+=",${rnd_bytes:0:2},${rnd_bytes:2:2}"
               done
               if [[ $j -lt $extn_len ]]; then
                    rnd_bytes="$(printf "%04x" $RANDOM)"
                    extn+=",${rnd_bytes:0:2}"
               fi
               if [[ $DEBUG -ge 2 ]]; then
                    echo -en "\nSending ClientHello with unrecognized extension"
                    [[ $DEBUG -ge 3 ]] && echo -n ": $extn"
                    echo ""
               fi
               tls_sockets "$proto" "$cipher_list" "" "$extn"
               success=$?
               if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then
                    break
               fi
          done
          if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then
               prln_svrty_medium " Server fails if ClientHello contains an unrecognized extension."
               outln "    extension used in failed test: $extn"
               fileout "$jsonID" "MEDIUM" "Server fails if ClientHello contains an unrecognized extension: $extn"
               bug_found=true
          else
               # Check for inability to handle empty last extension (see PR #792 and
               # https://www.ietf.org/mail-archive/web/tls/current/msg19720.html).
               # (Since this test also uses an unrecognized extension, only run this
               # test if the previous test passed, and use the final extension value
               # from that test to ensure that the only difference is the location
               # of the extension.)

               # The "extra extensions" parameter needs to include the padding and
               # heartbeat extensions, since otherwise prepare_tls_clienthello()
               # will add these extensions to the end of the ClientHello.
               debugme echo -e "\nSending ClientHello with empty last extension."
               tls_sockets "$proto" "$cipher_list" "" "
                 00,0f, 00,01, 01,
                 00,15, 00,56,
                   00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,
                   00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,
                   00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,
                   00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,
                 $extn"
               success=$?
               if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then
                    prln_svrty_medium " Server fails if last extension in ClientHello is empty."
                    fileout "$jsonID" "MEDIUM" "Server fails if last extension in ClientHello is empty."
                    bug_found=true
               fi
          fi
     fi

     # Check for SERVER_SIZE_LIMIT_BUG.
     # Send a ClientHello with 129 cipher suites (including 0x00,0xff) to see
     # if adding a 129th cipher to the list causes a failure.
#TODO: we need to clarify whether the mit is hit at 128 or 129 ciphers.
     if "$normal_hello_ok" && [[ "$proto" == 03 ]]; then
          debugme echo -e "\nSending ClientHello with 129 cipher suites."
          tls_sockets "$proto" "00,27, $cipher_list"
          success=$?
          if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then
               prln_svrty_medium " Server fails if ClientHello includes more than 128 cipher suites."
               fileout "$jsonID" "MEDIUM" "Server fails if ClientHello includes more than 128 cipher suites."
               SERVER_SIZE_LIMIT_BUG=true
               bug_found=true
          fi
     fi

     # Check for ClientHello size bug. According to RFC 7586 "at least one TLS
     # implementation is known to hang the connection when [a] ClientHello
     # record [with a length between 256 and 511 bytes] is received."
     # If the length of the host name is more than 75 bytes (which would make
     # $SNI more than 87 bytes), then the ClientHello would be more than 511
     # bytes if the server_name extension were included. Removing the SNI
     # extension, however, may not be an option, since the server may reject the
     # connection attempt for that reason.
     if "$normal_hello_ok" && [[ "$proto" != 00 ]] && [[ ${#SNI} -le 87 ]]; then
          # Normally prepare_tls_clienthello() will add a padding extension with a length
          # that will make the ClientHello be 512 bytes in length. Providing an "extra
          # extensions" parameter with a short padding extension prevents that.
          debugme echo -e "\nSending ClientHello with length between 256 and 511 bytes."
          tls_sockets "$proto" "$cipher_list" "" "00,15,00,01,00"
          success=$?
          if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then
               prln_svrty_medium " Server fails if ClientHello is between 256 and 511 bytes in length."
               fileout "$jsonID" "MEDIUM" "Server fails if ClientHello is between 256 and 511 bytes in length."
               bug_found=true
          fi
     fi

     # Check that server ignores unrecognized cipher suite values
     # see https://datatracker.ietf.org/doc/draft-ietf-tls-grease
     if "$normal_hello_ok"; then
          list=""
          for ciph in "${grease_cipher_suites[@]}"; do
               list+=", $ciph"
          done
          debugme echo -e "\nSending ClientHello with unrecognized cipher suite values."
          tls_sockets "$proto" "${list:2}, $selected_cipher_hex, 00,ff"
          success=$?
          if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then
               prln_svrty_medium " Server fails if ClientHello contains unrecognized cipher suite values."
               fileout "$jsonID" "MEDIUM" "Server fails if ClientHello contains unrecognized cipher suite values."
               bug_found=true
          fi
     fi

     # Check that servers that support ECDHE cipher suites ignore
     # unrecognized named group values.
     # see https://datatracker.ietf.org/doc/draft-ietf-tls-grease
     if [[ "$proto" != "00" ]]; then
          # Send a ClientHello that lists all of the ECDHE cipher suites
          tls_sockets "$proto" "$ecdhe_ciphers, 00,ff" "ephemeralkey"
          success=$?
          if [[ $success -eq 0 ]] || [[ $success -eq 2 ]]; then
               # Send the same ClientHello as before but with an unrecognized
               # named group value added. Make the unrecognized value the first
               # one in the list replacing one of the values in the original list,
               # but don't replace the value that was selected by the server.
               rnd=$RANDOM%${#grease_supported_groups[@]}
               temp=$(awk -F': ' '/^Server Temp Key/ { print $2 }' "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")
               curve_found="${temp%%,*}"
               if [[ "$curve_found" == "ECDH" ]]; then
                    curve_found="${temp#*, }"
                    curve_found="${curve_found%%,*}"
               fi
               if [[ "$curve_found" == "B-571" ]]; then
                    extn="
                    00, 0a,                    # Type: Supported Elliptic Curves , see RFC 4492
                    00, 3e, 00, 3c,            # lengths
                    ${grease_supported_groups[rnd]}, 00, 0e, 00, 19, 00, 1c, 00, 1e, 00, 0b, 00, 0c, 00, 1b,
                    00, 18, 00, 09, 00, 0a, 00, 1a, 00, 16, 00, 17, 00, 1d, 00, 08,
                    00, 06, 00, 07, 00, 14, 00, 15, 00, 04, 00, 05, 00, 12, 00, 13,
                    00, 01, 00, 02, 00, 03, 00, 0f, 00, 10, 00, 11"
               else
                    extn="
                    00, 0a,                    # Type: Supported Elliptic Curves , see RFC 4492
                    00, 3e, 00, 3c,            # lengths
                    ${grease_supported_groups[rnd]}, 00, 0d, 00, 19, 00, 1c, 00, 1e, 00, 0b, 00, 0c, 00, 1b,
                    00, 18, 00, 09, 00, 0a, 00, 1a, 00, 16, 00, 17, 00, 1d, 00, 08,
                    00, 06, 00, 07, 00, 14, 00, 15, 00, 04, 00, 05, 00, 12, 00, 13,
                    00, 01, 00, 02, 00, 03, 00, 0f, 00, 10, 00, 11"
               fi
               debugme echo -e "\nSending ClientHello with unrecognized named group value in supported_groups extension."
               tls_sockets "$proto" "$ecdhe_ciphers, 00,ff" "" "$extn"
               success=$?
               if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then
                    prln_svrty_medium " Server fails if ClientHello contains a supported_groups extension with an unrecognized named group value (${grease_supported_groups[rnd]})."
                    fileout "$jsonID" "MEDIUM" "Server fails if ClientHello contains a supported_groups extension with an unrecognized named group value (${grease_supported_groups[rnd]})."
                    bug_found=true
               fi
          fi
     fi

     # Check that servers that support the ALPN extension ignore
     # unrecognized ALPN values.
     # see https://datatracker.ietf.org/doc/draft-ietf-tls-grease
     if "$normal_hello_ok" && [[ -z $STARTTLS ]] && [[ "$proto" != "00" ]]; then
          for alpn_proto in $ALPN_PROTOs; do
               alpn+=",$(printf "%02x" ${#alpn_proto}),$(string_to_asciihex "$alpn_proto")"
          done
          alpn_list_len=${#alpn}/3
          alpn_list_len_hex=$(printf "%04x" $alpn_list_len)
          extn_len=$alpn_list_len+2
          extn_len_hex=$(printf "%04x" $extn_len)
          tls_sockets "$proto" "$cipher_list" "all" "00,10,${extn_len_hex:0:2},${extn_len_hex:2:2},${alpn_list_len_hex:0:2},${alpn_list_len_hex:2:2}$alpn"
          success=$?
          if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then
               prln_svrty_medium " Server fails if ClientHello contains an application_layer_protocol_negotiation extension."
               fileout "$jsonID" "MEDIUM" "Server fails if ClientHello contains an application_layer_protocol_negotiation extension."
               bug_found=true
          else
               selected_alpn_protocol="$(grep "ALPN protocol:" "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" | sed 's/ALPN protocol:  //')"
               # If using a "normal" ALPN extension worked, then add an unrecognized
               # ALPN value to the beginning of the extension and try again.
               alpn_proto="ignore/$selected_alpn_protocol"
               alpn=",$(printf "%02x" ${#alpn_proto}),$(string_to_asciihex "$alpn_proto")$alpn"
               alpn_list_len=${#alpn}/3
               alpn_list_len_hex=$(printf "%04x" $alpn_list_len)
               extn_len=$alpn_list_len+2
               extn_len_hex=$(printf "%04x" $extn_len)
               debugme echo -e "\nSending ClientHello with unrecognized ALPN value in application_layer_protocol_negotiation extension."
               tls_sockets "$proto" "$cipher_list" "all" "00,10,${extn_len_hex:0:2},${extn_len_hex:2:2},${alpn_list_len_hex:0:2},${alpn_list_len_hex:2:2}$alpn"
               success=$?
               if [[ $success -ne 0 ]] && [[ $success -ne 2 ]]; then
                    prln_svrty_medium " Server fails if ClientHello contains an application_layer_protocol_negotiation extension with an unrecognized ALPN value."
                    fileout "$jsonID" "MEDIUM" "erver fails if ClientHello contains an application_layer_protocol_negotiation extension with an unrecognized ALPN value."
                    bug_found=true
               else
                    grease_selected_alpn_protocol="$(grep "ALPN protocol:" "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt" | sed 's/ALPN protocol:  //')"
                    if [[ -z "$grease_selected_alpn_protocol" ]] && [[ -n "$selected_alpn_protocol" ]]; then
                         prln_svrty_medium " Server did not ignore unrecognized ALPN value in the application_layer_protocol_negotiation extension."
                         fileout "$jsonID" "MEDIUM" "Server did not ignore unrecognized ALPN value in the application_layer_protocol_negotiation extension."
                         bug_found=true
                    elif [[ "$grease_selected_alpn_protocol" =~ ignore/ ]]; then
                         prln_svrty_medium " Server selected \"ignore/\" ALPN value in the application_layer_protocol_negotiation extension."
                         fileout "$jsonID" "MEDIUM" "Server selected \"ignore/\" ALPN value in the application_layer_protocol_negotiation extension."
                         bug_found=true
                    fi
               fi
          fi
     fi

     # TODO: For servers that support TLSv1.3, check that servers ignore
     # an unrecognized named group value along with a corresponding
     # unrecognized key share
     # see https://www.ietf.org/mail-archive/web/tls/current/msg22322.html
     # and https://www.ietf.org/mail-archive/web/tls/current/msg22319.html

     # TODO: For servers that support TLSv1.3, check that servers ignore unrecognized
     # values in the supported_versions extension.
     # see https://www.ietf.org/mail-archive/web/tls/current/msg22319.html

     # TODO: For servers that support TLSv1.3, check that servers don't require the
     # psk_key_exchange_modes extension to be present in the ClientHello.

     if ! "$bug_found"; then
          outln " No bugs found."
          fileout "$jsonID" "OK" "No bugs found."
          #return 0
     else
          #return 1
          :
     fi
     return $ret
     #FIXME: No client side error cases where we want to return 1?
}

# If the server supports any non-PSK cipher suites that use RSA key transport,
# check if the server is vulnerable to Bleichenbacher's Oracle Threat (ROBOT) attacks.
# See "Return Of Bleichenbacher's Oracle Threat (ROBOT)" by Hanno Böck,
# Juraj Somorovsky, and Craig Young (https://robotattack.org).
#
run_robot() {
     local tls_hexcode="03"
     # A list of all non-PSK cipher suites that use RSA key transport
     local cipherlist="00,9d, c0,a1, c0,9d, 00,3d, 00,35, 00,c0, 00,84, c0,3d, c0,51, c0,7b, ff,00, ff,01, ff,02, ff,03, c0,a0, c0,9c, 00,9c, 00,3c, 00,2f, 00,ba, 00,96, 00,41, 00,07, c0,3c, c0,50, c0,7a, 00,05, 00,04, 00,0a, fe,ff, ff,e0, 00,62, 00,09, 00,61, fe,fe, ff,e1, 00,64, 00,60, 00,08, 00,06, 00,03, 00,3b, 00,02, 00,01"
     # A list of all non-PSK cipher suites that use RSA key transport and that use AES in either GCM or CBC mode.
     local aes_gcm_cbc_cipherlist="00,9d, 00,9c, 00,3d, 00,35, 00,3c, 00,2f"
     local padded_pms encrypted_pms cke_prefix client_key_exchange rnd_pad
     local rnd_pms="aa112233445566778899112233445566778899112233445566778899112233445566778899112233445566778899"
     local change_cipher_spec finished resp
     local -a response
     local -i i subret len iteration testnum pubkeybytes
     local pubkeybits
     local vulnerable=false send_ccs_finished=true
     local -i start_time end_time robottimeout=$MAX_WAITSOCK
     local cve="CVE-2017-17382 CVE-2017-17427 CVE-2017-17428 CVE-2017-13098 CVE-2017-1000385 CVE-2017-13099 CVE-2016-6883 CVE-2012-5081 CVE-2017-6168"
     local cwe="CWE-203"
     local jsonID="ROBOT"

     [[ $VULN_COUNT -le $VULN_THRESHLD ]] && outln && pr_headlineln " Testing for Return of Bleichenbacher's Oracle Threat (ROBOT) vulnerability " && outln
     pr_bold " ROBOT                                     "

     if ( [[ "$STARTTLS_PROTOCOL" =~ ldap ]] || [[ "$STARTTLS_PROTOCOL" =~ irc ]] ); then
          prln_local_problem "STARTTLS/$STARTTLS_PROTOCOL and --ssl-native collide here"
          return 1
     fi

     if [[ ! "$HAS_PKUTIL" ]]; then
          prln_local_problem "Your $OPENSSL does not support the pkeyutl utility."
          fileout "$jsonID" "WARN" "$OPENSSL does not support the pkeyutl utility." "$cve" "$cwe"
          return 1
     elif ! "$HAS_PKEY"; then
          prln_local_problem "Your $OPENSSL does not support the pkey utility."
          fileout "$jsonID" "WARN" "$OPENSSL does not support the pkey utility." "$cve" "$cwe"
          return 1
     fi

     if [[ 0 -eq $(has_server_protocol tls1_2) ]]; then
          tls_hexcode="03"
     elif [[ 0 -eq $(has_server_protocol tls1_1) ]]; then
          tls_hexcode="02"
     elif [[ 0 -eq $(has_server_protocol tls1) ]]; then
          tls_hexcode="01"
     elif [[ 0 -eq $(has_server_protocol ssl3) ]]; then
          tls_hexcode="00"
     fi

     # Some hosts are only vulnerable with GCM. First send a list of
     # ciphers that use AES in GCM or CBC mode, with the GCM ciphers
     # listed first, and then try all ciphers that use RSA key transport
     # if there is no connection on the first try.
     tls_sockets "$tls_hexcode" "$aes_gcm_cbc_cipherlist, 00,ff"
     subret=$?
     if [[ $subret -eq 0 ]] || [[ $subret -eq 2 ]]; then
          cipherlist="$aes_gcm_cbc_cipherlist"
          tls_hexcode="${DETECTED_TLS_VERSION:2:2}"
     else
          if [[ "$tls_hexcode" != "03" ]]; then
               cipherlist="$(strip_inconsistent_ciphers "$tls_hexcode" ", $cipherlist")"
               cipherlist="${cipherlist:2}"
          fi
          tls_sockets "$tls_hexcode" "$cipherlist, 00,ff"
          subret=$?
          if [[ $subret -eq 2 ]]; then
               tls_hexcode="${DETECTED_TLS_VERSION:2:2}"
               cipherlist="$(strip_inconsistent_ciphers "$tls_hexcode" ", $cipherlist")"
               cipherlist="${cipherlist:2}"
          elif [[ $subret -ne 0 ]]; then
               prln_svrty_best "Server does not support any cipher suites that use RSA key transport"
               fileout "$jsonID" "OK" "not vulnerable, no RSA key transport cipher" "$cve" "$cwe"
               return 0
          fi
     fi

     # Run the tests in two iterations. In iteration 0, send 5 different client
     # key exchange (CKE) messages followed by change cipher spec (CCS) and
     # Finished messages, and check whether the server provided the same
     # response in each case. If the server didn't provide the same response
     # for all five messages in iteration 0, then it is vulnerable. Otherwise
     # try a second time (iteration 1) with the same CKE messages, but without
     # sending the CCS or Finished messages.
     # Iterations 0 and 1 are run with a short timeout waiting for the server
     # to respond to the CKE message. If the server was found to be potentially
     # vulnerable in iteration 0 or 1 and testssl.sh timed out waiting for a
     # response in some cases, then retry the test using a longer timeout value.
     for (( iteration=0; iteration < 3; iteration++ )); do
          if [[ $iteration -eq 1 ]]; then
               # If the server was found to be vulnerable in iteration 0, then
               # there's no need to try the alternative message flow.
               "$vulnerable" && continue
               send_ccs_finished=false
          elif [[ $iteration -eq 2 ]]; then
               # The tests are being rerun, so reset the vulnerable flag.
               vulnerable=false
          fi
          for (( testnum=0; testnum < 5; testnum++ )); do
               response[testnum]="untested"
          done
          for (( testnum=0; testnum < 5; testnum++ )); do
               tls_sockets "$tls_hexcode" "$cipherlist, 00,ff" "all" "" "" "false"

               # Create the padded premaster secret to encrypt. The padding should be
               # of the form "00 02 <random> 00 <TLS version> <premaster secret>."
               # However, for each test except testnum=0 the padding will be
               # made incorrect in some way, as specified below.

               # Determine the length of the public key and create the <random> bytes.
               # <random> should be a length that makes total length of $padded_pms
               # the same as the length of the public key. <random> should contain no 00 bytes.
               pubkeybits="$($OPENSSL x509 -noout -pubkey -in $HOSTCERT 2>>$ERRFILE | \
                             $OPENSSL pkey -pubin -text 2>>$ERRFILE | awk -F'(' '/Public-Key/ { print $2 }')"
               pubkeybits="${pubkeybits%%bit*}"
               pubkeybytes=$pubkeybits/8
               [[ $((pubkeybits%8)) -ne 0 ]] && pubkeybytes+=1
               rnd_pad=""
               for (( len=0; len < pubkeybytes-52; len+=2 )); do
                    rnd_pad+="abcd"
               done
               [[ $len -eq $pubkeybytes-52 ]] && rnd_pad+="ab"

               case "$testnum" in
                    # correct padding
                    0) padded_pms="0002${rnd_pad}00${DETECTED_TLS_VERSION}${rnd_pms}" ;;
                    # wrong first two bytes
                    1) padded_pms="4117${rnd_pad}00${DETECTED_TLS_VERSION}${rnd_pms}" ;;
                    # 0x00 on a wrong position
                    2) padded_pms="0002${rnd_pad}11${rnd_pms}0011" ;;
                    # no 0x00 in the middle
                    3) padded_pms="0002${rnd_pad}111111${rnd_pms}" ;;
                    # wrong version number (according to Klima / Pokorny / Rosa paper)
                    4) padded_pms="0002${rnd_pad}000202${rnd_pms}" ;;
               esac

               # Encrypt the padded premaster secret using the server's public key.
               encrypted_pms="$(asciihex_to_binary "$padded_pms" | \
                    $OPENSSL pkeyutl -encrypt -certin -inkey $HOSTCERT -pkeyopt rsa_padding_mode:none 2>/dev/null | \
                    hexdump -v -e '16/1 "%02x"')"
               if [[ -z "$encrypted_pms" ]]; then
                    if [[ "$DETECTED_TLS_VERSION" == "0300" ]]; then
                         socksend ",x15, x03, x00, x00, x02, x02, x00" 0
                    else
                         socksend ",x15, x03, x01, x00, x02, x02, x00" 0
                    fi
                    close_socket
                    prln_fixme "Conversion of public key failed around line $((LINENO - 9))"
                    fileout "$jsonID" "WARN" "Conversion of public key failed around line $((LINENO - 10)) "
                    return 1
               fi

               # Create the client key exchange message.
               len=${#encrypted_pms}/2
               cke_prefix="16${DETECTED_TLS_VERSION}$(printf "%04x" $((len+6)))10$(printf "%06x" $((len+2)))$(printf "%04x" $len)"
               encrypted_pms="$cke_prefix$encrypted_pms"
               len=${#encrypted_pms}
               client_key_exchange=""
               for (( i=0; i<len; i+=2 )); do
                    client_key_exchange+=", x${encrypted_pms:i:2}"
               done

               # The contents of change cipher spec are fixed.
               change_cipher_spec=", x14, x${DETECTED_TLS_VERSION:0:2}, x${DETECTED_TLS_VERSION:2:2}, x00, x01, x01"

               # Send an arbitrary Finished message.
               finished=", x16, x${DETECTED_TLS_VERSION:0:2}, x${DETECTED_TLS_VERSION:2:2}
                         , x00, x40, x6e, x49, x65, x68, x00, x46, x79, xfd, x5a, x57, xdc
                         , x3e, xef, xb2, xd2, xac, xe0, x8c, x54, x2d, x5f, x00, x87, xdb
                         , xb6, xe3, x77, x2c, x9d, x88, x27, x38, x98, x7d, xcd, x7e, xac
                         , xdd, x5d, x72, xbe, x24, x0d, x20, x36, x14, x0e, x94, x51, xde
                         , xa0, xb6, xc7, x56, x28, xd8, xa1, xcb, x24, xb9, x03, xd0, x7c, x50"

               if "$send_ccs_finished"; then
                    debugme echo -en "\nsending client key exchange, change cipher spec, finished... "
                    socksend "$client_key_exchange$change_cipher_spec$finished" $USLEEP_SND
               else
                    debugme echo -en "\nsending client key exchange... "
                    socksend "$client_key_exchange" $USLEEP_SND
               fi
               debugme echo "reading server error response..."
               start_time=$(LC_ALL=C date "+%s")
               sockread_serverhello 32768 $robottimeout
               subret=$?
               if [[ $subret -eq 0 ]]; then
                    end_time=$(LC_ALL=C date "+%s")
                    resp=$(hexdump -v -e '16/1 "%02x"' "$SOCK_REPLY_FILE")
                    response[testnum]="${resp%%[!0-9A-F]*}"
                    # The first time a response is received to a client key
                    # exchange message, measure the amount of time it took to
                    # receive a response and set the timeout value for future
                    # tests to 2 seconds longer than it took to receive a response.
                    [[ $iteration -ne 2 ]] && [[ $robottimeout -eq $MAX_WAITSOCK ]] && \
                         [[ $((end_time-start_time)) -lt $((MAX_WAITSOCK-2)) ]] && \
                         robottimeout=$((end_time-start_time+2))
               else
                    response[testnum]="Timeout waiting for alert"
               fi
               debugme echo -e "\nresponse[$testnum] = ${response[testnum]}"
               [[ $DEBUG -ge 3 ]] && [[ $subret -eq 0 ]] && parse_tls_serverhello "${response[testnum]}"
               close_socket

               # Don't continue testing if it has already been determined that
               # tests need to be rerun with a longer timeout.
               if [[ $iteration -ne 2 ]]; then
                    for (( i=1; i <= testnum; i++ )); do
                         if [[ "${response[i]}" != "${response[$((i-1))]}" ]] && \
                            ( [[ "${response[i]}" == "Timeout waiting for alert" ]] || \
                              [[ "${response[$((i-1))]}" == "Timeout waiting for alert" ]] ); then
                              vulnerable=true
                              break
                         fi
                    done
                    "$vulnerable" && break
               fi
               # Don't continue testing if it has already been determined that the server is
               # strongly vulnerable.
               if [[ $testnum -eq 2 ]]; then
                    [[ "${response[1]}" != "${response[2]}" ]] && break
               elif [[ $testnum -eq 3 ]]; then
                    [[ "${response[2]}" != "${response[3]}" ]] && break
                    [[ "${response[0]}" != "${response[1]}" ]] && break
               fi
          done
          # If the server provided the same error message for all tests, then this
          # is an indication that the server is not vulnerable.
          if [[ "${response[0]}" != "${response[1]}" ]] || [[ "${response[1]}" != "${response[2]}" ]] || \
             [[ "${response[2]}" != "${response[3]}" ]] || [[ "${response[3]}" != "${response[4]}" ]]; then
               vulnerable=true

               # If the test was run with a short timeout and was found to be
               # potentially vulnerable due to some tests timing out, then
               # verify the results by rerunning with a longer timeout.
               if [[ $robottimeout -eq $MAX_WAITSOCK ]]; then
                    break
               elif [[ "${response[0]}" == "Timeout waiting for alert" ]] || \
                    [[ "${response[1]}" == "Timeout waiting for alert" ]] || \
                    [[ "${response[2]}" == "Timeout waiting for alert" ]] || \
                    [[ "${response[3]}" == "Timeout waiting for alert" ]] || \
                    [[ "${response[4]}" == "Timeout waiting for alert" ]]; then
                    robottimeout=10
               else
                    break
               fi
          fi
          ! "$vulnerable" && [[ $iteration -eq 1 ]] && break
     done

     if "$vulnerable"; then
          if [[ "${response[1]}" == "${response[2]}" ]] && [[ "${response[2]}" == "${response[3]}" ]]; then
               pr_svrty_medium "VULNERABLE (NOT ok)"; outln " - weakly vulnerable as the attack would take too long"
               fileout "$jsonID" "MEDIUM" "VULNERABLE, but the attack would take too long" "$cve" "$cwe"
          else
               prln_svrty_critical "VULNERABLE (NOT ok)"
               fileout "$jsonID" "CRITICAL" "VULNERABLE" "$cve" "$cwe"
          fi
          set_grade_cap "F" "Vulnerable to ROBOT"
     else
          prln_svrty_best "not vulnerable (OK)"
          fileout "$jsonID" "OK" "not vulnerable" "$cve" "$cwe"
     fi
     return 0
}

old_fart() {
     out "Get precompiled bins or compile "
     pr_url "https://github.com/PeterMosmans/openssl"
     outln "."
     fileout_insert_warning "old_fart" "WARN" "Your $OPENSSL $OSSL_VER version is an old fart... . It doesn\'t make much sense to proceed. Get precompiled bins or compile https://github.com/PeterMosmans/openssl ."
     fatal "Your $OPENSSL $OSSL_VER version is an old fart... . It doesn't make much sense to proceed." $ERR_OSSLBIN
}

# try very hard to determine the install path to get ahold of the mapping file and the CA bundles
# TESTSSL_INSTALL_DIR can be supplied via environment so that the cipher mapping and CA bundles can be found
# www.carbonwind.net/TLS_Cipher_Suites_Project/tls_ssl_cipher_suites_simple_table_all.htm
get_install_dir() {
     [[ -z "$TESTSSL_INSTALL_DIR" ]] && TESTSSL_INSTALL_DIR="$(dirname "${BASH_SOURCE[0]}")"

     if [[ -r "$RUN_DIR/etc/cipher-mapping.txt" ]]; then
          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"
          [[ -r "$TESTSSL_INSTALL_DIR/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/cipher-mapping.txt"
     fi

     # we haven't found the cipher file yet...
     if [[ ! -r "$CIPHERS_BY_STRENGTH_FILE" ]] && type -p readlink &>/dev/null ; then
          readlink -f ls &>/dev/null && \
               TESTSSL_INSTALL_DIR="$(readlink -f "$(basename "${BASH_SOURCE[0]}")")" || \
               TESTSSL_INSTALL_DIR="$(readlink "$(basename "${BASH_SOURCE[0]}")")"
               # not sure whether Darwin has -f
          TESTSSL_INSTALL_DIR="$(dirname "$TESTSSL_INSTALL_DIR" 2>/dev/null)"
          [[ -r "$TESTSSL_INSTALL_DIR/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/cipher-mapping.txt"
          [[ -r "$TESTSSL_INSTALL_DIR/etc/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/etc/cipher-mapping.txt"
     fi

     # still no cipher mapping file:
     if [[ ! -r "$CIPHERS_BY_STRENGTH_FILE" ]] && type -p realpath &>/dev/null ; then
          TESTSSL_INSTALL_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
          CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/etc/cipher-mapping.txt"
          [[ -r "$TESTSSL_INSTALL_DIR/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/cipher-mapping.txt"
     fi

     # still no cipher mapping file (and realpath is not present):
     if [[ ! -r "$CIPHERS_BY_STRENGTH_FILE" ]] && type -p readlink &>/dev/null ; then
          readlink -f ls &>/dev/null && \
               TESTSSL_INSTALL_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" || \
               TESTSSL_INSTALL_DIR="$(dirname "$(readlink "${BASH_SOURCE[0]}")")"
               # not sure whether Darwin has -f
          CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/etc/cipher-mapping.txt"
          [[ -r "$TESTSSL_INSTALL_DIR/cipher-mapping.txt" ]] && CIPHERS_BY_STRENGTH_FILE="$TESTSSL_INSTALL_DIR/cipher-mapping.txt"
     fi

     if [[ ! -r "$CIPHERS_BY_STRENGTH_FILE" ]]; then
          DISPLAY_CIPHERNAMES="openssl-only"
          debugme echo "$CIPHERS_BY_STRENGTH_FILE"
          prln_warning "\nATTENTION: No cipher mapping file found!"
          outln "Please note from 2.9 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 $ERR_RESOURCE
     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.9 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 $ERR_RESOURCE
     else
          :     # see #705, in a nutshell: not portable to initialize a global array inside a function. Thus it'll be done in main part below
     fi
}


test_openssl_suffix() {
     local naming_ext="$(uname).$(uname -m)"
     local uname_arch="$(uname -m)"
     local myarch_suffix=""

     [[ $uname_arch =~ 64 ]] && myarch_suffix=64 || myarch_suffix=32
     if [[ -f "$1/openssl" ]] && [[ -x "$1/openssl" ]]; then
          OPENSSL="$1/openssl"
          return 0
     elif [[ -f "$1/openssl.$naming_ext" ]] && [[ -x "$1/openssl.$naming_ext" ]]; then
          OPENSSL="$1/openssl.$naming_ext"
          return 0
     elif [[ -f "$1/openssl.$uname_arch" ]] && [[ -x "$1/openssl.$uname_arch" ]]; then
          OPENSSL="$1/openssl.$uname_arch"
          return 0
     elif [[ -f "$1/openssl$myarch_suffix" ]] && [[ -x "$1/openssl$myarch_suffix" ]]; then
          OPENSSL="$1/openssl$myarch_suffix"
          return 0
     fi
     return 1
}


find_openssl_binary() {
     local s_client_has=$TEMPDIR/s_client_has.txt
     local s_client_starttls_has=$TEMPDIR/s_client_starttls_has.txt
     local openssl_location cwd=""
     local ossl_wo_dev_info
     local curve
     local -a curves_ossl=("sect163k1" "sect163r1" "sect163r2" "sect193r1" "sect193r2" "sect233k1" "sect233r1" "sect239k1" "sect283k1" "sect283r1" "sect409k1" "sect409r1" "sect571k1" "sect571r1" "secp160k1" "secp160r1" "secp160r2" "secp192k1" "prime192v1" "secp224k1" "secp224r1" "secp256k1" "prime256v1" "secp384r1" "secp521r1" "brainpoolP256r1" "brainpoolP384r1" "brainpoolP512r1" "X25519" "X448")

     # 0. check environment variable whether it's executable
     if [[ -n "$OPENSSL" ]] && [[ ! -x "$OPENSSL" ]]; then
          prln_warning "\ncannot find specified (\$OPENSSL=$OPENSSL) binary."
          tmln_out " Looking some place else ..."
     elif [[ -x "$OPENSSL" ]]; then
          :    # 1. all ok supplied $OPENSSL was found and has executable bit set -- testrun comes below
     elif [[ -e "/mnt/c/Windows/System32/bash.exe" ]] && test_openssl_suffix "$(dirname "$(type -p openssl)")"; then
          # 2. otherwise, only if on Bash on Windows, use system binaries only.
          SYSTEM2="WSL"
     elif test_openssl_suffix "$TESTSSL_INSTALL_DIR"; then
          :    # 3. otherwise try openssl in path of testssl.sh
     elif test_openssl_suffix "$TESTSSL_INSTALL_DIR/bin"; then
          :    # 4. otherwise here, this is supposed to be the standard --platform independent path in the future!!!
     elif test_openssl_suffix "$(dirname "$(type -p openssl)")"; then
          :    # 5. we tried hard and failed, so now we use the system binaries
     fi

     # no ERRFILE initialized yet, thus we use /dev/null for stderr directly
     $OPENSSL version -a 2>/dev/null >/dev/null
     if [[ $? -ne 0 ]] || [[ ! -x "$OPENSSL" ]]; then
          fatal "cannot exec or find any openssl binary" $ERR_OSSLBIN
     fi

     # https://www.openssl.org/news/changelog.html
     # https://web.archive.org/web/20150815130800/http://openssl.org/news/openssl-notes.html
     OSSL_NAME=$($OPENSSL version 2>/dev/null | awk '{ print $1 }')
     OSSL_VER=$($OPENSSL version 2>/dev/null | awk -F' ' '{ print $2 }')
     OSSL_VER_MAJOR="${OSSL_VER%%\.*}"
     ossl_wo_dev_info="${OSSL_VER%%-*}"
     OSSL_VER_MINOR="${ossl_wo_dev_info#$OSSL_VER_MAJOR\.}"
     OSSL_VER_MINOR="${OSSL_VER_MINOR%%[a-zA-Z]*}"
     OSSL_VER_APPENDIX="${OSSL_VER#$OSSL_VER_MAJOR\.$OSSL_VER_MINOR}"
     OSSL_VER_PLATFORM=$($OPENSSL version -p 2>/dev/null | sed 's/^platform: //')
     OSSL_BUILD_DATE=$($OPENSSL version -a 2>/dev/null | grep '^built' | sed -e 's/built on//' -e 's/: ... //' -e 's/: //' -e 's/ UTC//' -e 's/ +0000//' -e 's/.000000000//')

     # see #190, reverting logic: unless otherwise proved openssl has no dh bits
     case "$OSSL_VER_MAJOR.$OSSL_VER_MINOR" in
          1.0.2|1.1.0|1.1.1|3.0.0) HAS_DH_BITS=true ;;
     esac
     if [[ "$OSSL_NAME" =~ LibreSSL ]]; then
          [[ ${OSSL_VER//./} -ge 210 ]] && HAS_DH_BITS=true
          if "$SSL_NATIVE"; then
               outln
               pr_warning "LibreSSL in native ssl mode is not a good choice for testing INSECURE features!"
          fi
     fi

     initialize_engine

     openssl_location="$(type -p $OPENSSL)"
     [[ -n "$GIT_REL" ]] && \
          cwd="$PWD" || \
          cwd="$RUN_DIR"
     if [[ "$openssl_location" == ${PWD}/bin ]]; then
          OPENSSL_LOCATION="\$PWD/bin/$(basename "$openssl_location")"
     elif [[ "$openssl_location" =~ $cwd ]] && [[ "$cwd" != '.' ]]; then
          OPENSSL_LOCATION="${openssl_location%%$cwd}"
     else
          OPENSSL_LOCATION="$openssl_location"
     fi

     OSSL_CIPHERS_S=""
     HAS_SSL2=false
     HAS_SSL3=false
     HAS_TLS13=false
     HAS_X448=false
     HAS_X25519=false
     HAS_SIGALGS=false
     HAS_NO_SSL2=false
     HAS_NOSERVERNAME=false
     HAS_CIPHERSUITES=false
     HAS_SECLEVEL=false
     HAS_COMP=false
     HAS_NO_COMP=false
     HAS_CURVES=false
     OSSL_SUPPORTED_CURVES=""
     HAS_PKEY=false
     HAS_PKUTIL=false
     HAS_ALPN=false
     HAS_NPN=false
     HAS_FALLBACK_SCSV=false
     HAS_PROXY=false
     HAS_XMPP=false
     HAS_XMPP_SERVER=false
     HAS_POSTGRES=false
     HAS_MYSQL=false
     HAS_LMTP=false
     HAS_NNTP=false
     HAS_IRC=false
     HAS_CHACHA20=false
     HAS_AES128_GCM=false
     HAS_AES256_GCM=false
     HAS_ZLIB=false

     $OPENSSL ciphers -s 2>&1 | grep -aiq "unknown option" || \
          OSSL_CIPHERS_S="-s"

     # This and all other occurences we do a little trick using "invalid." to avoid plain and
     # link level DNS lookups. See issue #1418 and https://tools.ietf.org/html/rfc6761#section-6.4
     $OPENSSL s_client -ssl2 -connect invalid. 2>&1 | grep -aiq "unknown option" || \
          HAS_SSL2=true

     $OPENSSL s_client -ssl3 -connect invalid. 2>&1 | grep -aiq "unknown option" || \
          HAS_SSL3=true

     $OPENSSL s_client -tls1_3 -connect invalid. 2>&1 | grep -aiq "unknown option" || \
          HAS_TLS13=true

     $OPENSSL genpkey -algorithm X448 2>&1 | grep -aq "not found" || \
          HAS_X448=true

     $OPENSSL genpkey -algorithm X25519 2>&1 | grep -aq "not found" || \
          HAS_X25519=true

     if "$HAS_TLS13"; then
          $OPENSSL s_client -tls1_3 -sigalgs PSS+SHA256:PSS+SHA384 -connect invalid. 2>&1 | grep -aiq "unknown option" || \
               HAS_SIGALGS=true
     fi
     $OPENSSL s_client -no_ssl2 -connect invalid. 2>&1 | grep -aiq "unknown option" || \
          HAS_NO_SSL2=true

     $OPENSSL s_client -noservername -connect invalid. 2>&1 | grep -aiq "unknown option" || \
          HAS_NOSERVERNAME=true

     $OPENSSL s_client -ciphersuites -connect invalid. 2>&1 | grep -aiq "unknown option" || \
          HAS_CIPHERSUITES=true

     $OPENSSL ciphers @SECLEVEL=0:ALL > /dev/null 2> /dev/null
     [[ $? -eq 0 ]] && HAS_SECLEVEL=true

     $OPENSSL s_client -comp -connect invalid. 2>&1 | grep -aiq "unknown option" || \
          HAS_COMP=true

     $OPENSSL s_client -no_comp -connect invalid. 2>&1 | grep -aiq "unknown option" || \
          HAS_NO_COMP=true

     OPENSSL_NR_CIPHERS=$(count_ciphers "$(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL' 'ALL')")

     if $OPENSSL s_client -curves "${curves_ossl[0]}" -connect invalid. 2>&1 | grep -aiq "unknown option"; then
          for curve in "${curves_ossl[@]}"; do
               $OPENSSL s_client -groups $curve -connect invalid.:8443 2>&1 | grep -Eiaq "Error with command|unknown option|Failed to set groups"
               [[ $? -ne 0 ]] && OSSL_SUPPORTED_CURVES+=" $curve "
          done
     else
          HAS_CURVES=true
          for curve in "${curves_ossl[@]}"; do
               $OPENSSL s_client -curves $curve -connect invalid. 2>&1 | grep -Eiaq "Error with command|unknown option"
               [[ $? -ne 0 ]] && OSSL_SUPPORTED_CURVES+=" $curve "
          done
     fi

     $OPENSSL pkey -help 2>&1 | grep -q Error || \
          HAS_PKEY=true

     $OPENSSL pkeyutl 2>&1 | grep -q Error || \
          HAS_PKUTIL=true

     # For the following we feel safe enough to query the s_client help functions.
     # That was not good enough for the previous lookups
     $OPENSSL s_client -help 2>$s_client_has

     $OPENSSL s_client -starttls foo 2>$s_client_starttls_has

     grep -qw '\-alpn' $s_client_has && \
          HAS_ALPN=true

     grep -qw '\-nextprotoneg' $s_client_has && \
          HAS_NPN=true

     grep -qw '\-fallback_scsv' $s_client_has && \
          HAS_FALLBACK_SCSV=true

     grep -q '\-proxy' $s_client_has && \
          HAS_PROXY=true

     grep -q 'xmpp' $s_client_starttls_has && \
          HAS_XMPP=true

     grep -q 'xmpp-server' $s_client_starttls_has && \
          HAS_XMPP_SERVER=true

     grep -q 'postgres' $s_client_starttls_has && \
          HAS_POSTGRES=true

     grep -q 'mysql' $s_client_starttls_has && \
          HAS_MYSQL=true

     grep -q 'lmtp' $s_client_starttls_has && \
          HAS_LMTP=true

     grep -q 'nntp' $s_client_starttls_has && \
          HAS_NNTP=true

     grep -q 'irc' $s_client_starttls_has && \
          HAS_IRC=true

     $OPENSSL enc -chacha20 -K 12345678901234567890123456789012 -iv 01000000123456789012345678901234 > /dev/null 2> /dev/null <<< "test"
     [[ $? -eq 0 ]] && HAS_CHACHA20=true

     $OPENSSL enc -aes-128-gcm -K 0123456789abcdef0123456789abcdef -iv 0123456789abcdef01234567  > /dev/null 2> /dev/null <<< "test"
     [[ $? -eq 0 ]] && HAS_AES128_GCM=true

     $OPENSSL enc -aes-256-gcm -K 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef -iv 0123456789abcdef01234567  > /dev/null 2> /dev/null <<< "test"
     [[ $? -eq 0 ]] && HAS_AES256_GCM=true

     [[ "$(echo -e "\x78\x9C\xAB\xCA\xC9\x4C\xE2\x02\x00\x06\x20\x01\xBC" | $OPENSSL zlib -d 2>/dev/null)" == zlib ]] && HAS_ZLIB=true

     if [[ -n "$CONNECT_TIMEOUT" ]] || [[ -n "$OPENSSL_TIMEOUT" ]]; then
          # We don't set a general timeout as we might not have "timeout" installed and we only
          # do what is instructed. Thus we check first what the command line params were,
          # then we proceed
          if type -p timeout >/dev/null 2>&1; then
               # There are different versions of "timeout". Check whether --preserve-status is supported
               if timeout --help 2>/dev/null | grep -q 'preserve-status'; then
                    TIMEOUT_CMD="timeout --preserve-status"
               else
                    TIMEOUT_CMD="timeout"
               fi
          else
               TIMEOUT_CMD=""
               outln
               fatal "You specified a connect or openssl timeout but the binary \"timeout\" couldn't be found " $ERR_RESOURCE
          fi
     fi

     if ! "$do_mass_testing"; then
          if [[ -n $OPENSSL_TIMEOUT ]]; then
               OPENSSL="$TIMEOUT_CMD $OPENSSL_TIMEOUT $OPENSSL"
          fi
     fi

     return 0
}


check4openssl_oldfarts() {
     case "$OSSL_VER" in
          0.9.7*|0.9.6*|0.9.5*)
               # 0.9.5a was latest in 0.9.5 an released 2000/4/1, that'll NOT suffice for this test
               old_fart ;;
          0.9.8)
               case $OSSL_VER_APPENDIX in
                    a|b|c|d|e) old_fart;; # no SNI!
                    # other than that we leave this for MacOSX and FreeBSD but it's a pain and likely gives false negatives/positives
               esac
               ;;
     esac
     if [[ $OSSL_VER_MAJOR -lt 1 ]]; then ## mm: Patch for libressl
          prln_warning " Your \"$OPENSSL\" is way too old (<version 1.0) !"
          case $SYSTEM in
               *BSD|Darwin)
                    out " Please use binary provided in \$INSTALLDIR/bin/ or from ports/brew or compile from "
                    pr_url "github.com/PeterMosmans/openssl"; outln "."
                    fileout_insert_warning "too_old_openssl" "WARN" "Your $OPENSSL $OSSL_VER version is way too old. Please use binary provided in \$INSTALLDIR/bin/ or from ports/brew or compile from github.com/PeterMosmans/openssl ." ;;
               *)   out " Update openssl binaries or compile from "
                    pr_url "https://github.com/PeterMosmans/openssl"; outln "."
                    fileout_insert_warning "too_old_openssl" "WARN" "Update openssl binaries or compile from https://github.com/PeterMosmans/openssl .";;
          esac
          ignore_no_or_lame " Type \"yes\" to accept false negatives or positives" "yes"
          [[ $? -ne 0 ]] && exit $ERR_CLUELESS
     fi
     outln
}

# FreeBSD needs to have /dev/fd mounted. This is a friendly hint, see #258
check_bsd_mount() {
     if [[ "$(uname)" == FreeBSD ]]; then
          if ! mount | grep -q "^devfs"; then
               outln "you seem to run $PROG_NAME= in a jail. Hopefully you're did \"mount -t fdescfs fdesc /dev/fd\""
          elif mount | grep '/dev/fd' | grep -q fdescfs; then
               :
          else
               fatal "You need to mount fdescfs on FreeBSD: \"mount -t fdescfs fdesc /dev/fd\"" $ERR_OTHERCLIENT
          fi
     fi
}

# This sets the PRINTF command for writing into TCP sockets. It is needed because
# The shell builtin printf flushes the write buffer at every \n, ("\x0a") which
# in turn means a new TCP fragment. That causes a slight performance penalty and
# and some F5s to hiccup, see #1113. Unfortunately this can be used only with GNU's
# and OpenBSD's /usr/bin/printf -- FreeBSD + OS X can't do this. Thus here we need
# to pipe through dd or cat, see socksend() and socksend_clienthello(). An empty
# $PRINTF signals the bash internal printf which then uses cat as a stdout buffer.
# A better solution needs to follow.
#
choose_printf() {
     local p ptf

     ptf="$(type -aP printf)"
     if [[ -n "$ptf" ]]; then
          for p in $ptf; do
               if $p "\xc0\x14\xc0\xff\xee" | hexdump -C | grep -q 'c0 14 c0 ff ee'; then
                    PRINTF=$p
                    return 0
               fi
          done
     fi
     if type -t printf >/dev/null; then
          PRINTF=""
          return 0
     fi
     fatal "Neither external printf nor shell internal found. " $ERR_CLUELESS
}


help() {
     cat << EOF

     "$PROG_NAME [options] <URI>"    or    "$PROG_NAME <options>"


"$PROG_NAME <options>", where <options> is:

     --help                        what you're looking at
     -b, --banner                  displays banner + version of $PROG_NAME
     -v, --version                 same as previous
     -V, --local                   pretty print all local ciphers
     -V, --local <pattern>         which local ciphers with <pattern> are available? If pattern is not a number: word match

     <pattern>                     is always an ignore case word pattern of cipher hexcode or any other string in the name, kx or bits

"$PROG_NAME <URI>", where <URI> is:

     <URI>                         host|host:port|URL|URL:port   port 443 is default, URL can only contain HTTPS protocol)

"$PROG_NAME [options] <URI>", where [options] is:

     -t, --starttls <protocol>     Does a default run against a STARTTLS enabled <protocol,
                                   protocol is <ftp|smtp|lmtp|pop3|imap|xmpp|xmpp-server|telnet|ldap|nntp|postgres|mysql>
     --xmpphost <to_domain>        For STARTTLS enabled XMPP it supplies the XML stream to-'' domain -- sometimes needed
     --mx <domain/host>            Tests MX records from high to low priority (STARTTLS, port 25)
     --file/-iL <fname>            Mass testing option: Reads one testssl.sh command line per line from <fname>.
                                   Can be combined with --serial or --parallel. Implicitly turns on "--warnings batch".
                                   Text format 1: Comments via # allowed, EOF signals end of <fname>
                                   Text format 2: nmap output in greppable format (-oG), 1 port per line allowed
     --mode <serial|parallel>      Mass testing to be done serial (default) or parallel (--parallel is shortcut for the latter)
     --warnings <batch|off>        "batch" doesn't continue when a testing error is encountered, off continues and skips warnings
     --connect-timeout <seconds>   useful to avoid hangers. Max <seconds> to wait for the TCP socket connect to return
     --openssl-timeout <seconds>   useful to avoid hangers. Max <seconds> to wait before openssl connect will be terminated

single check as <options>  ("$PROG_NAME URI" does everything except -E and -g):
     -e, --each-cipher             checks each local cipher remotely
     -E, --cipher-per-proto        checks those per protocol
     -s, --std, --standard         tests certain lists of cipher suites by strength
     -f, --fs, --nsa               checks forward secrecy settings
     -p, --protocols               checks TLS/SSL protocols (including SPDY/HTTP2)
     -g, --grease                  tests several server implementation bugs like GREASE and size limitations
     -S, --server-defaults         displays the server's default picks and certificate info
     -P, --server-preference       displays the server's picks: protocol+cipher
     -x, --single-cipher <pattern> tests matched <pattern> of ciphers
                                   (if <pattern> not a number: word match)
     -c, --client-simulation       test client simulations, see which client negotiates with cipher and protocol
     -h, --header, --headers       tests HSTS, HPKP, server/app banner, security headers, cookie, reverse proxy, IPv4 address

     -U, --vulnerable              tests all (of the following) vulnerabilities (if applicable)
     -H, --heartbleed              tests for Heartbleed vulnerability
     -I, --ccs, --ccs-injection    tests for CCS injection vulnerability
     -T, --ticketbleed             tests for Ticketbleed vulnerability in BigIP loadbalancers
     -BB, --robot                  tests for Return of Bleichenbacher's Oracle Threat (ROBOT) vulnerability
     -R, --renegotiation           tests for renegotiation vulnerabilities
     -C, --compression, --crime    tests for CRIME vulnerability (TLS compression issue)
     -B, --breach                  tests for BREACH vulnerability (HTTP compression issue)
     -O, --poodle                  tests for POODLE (SSL) vulnerability
     -Z, --tls-fallback            checks TLS_FALLBACK_SCSV mitigation
     -W, --sweet32                 tests 64 bit block ciphers (3DES, RC2 and IDEA): SWEET32 vulnerability
     -A, --beast                   tests for BEAST vulnerability
     -L, --lucky13                 tests for LUCKY13
     -F, --freak                   tests for FREAK vulnerability
     -J, --logjam                  tests for LOGJAM vulnerability
     -D, --drown                   tests for DROWN vulnerability
     -4, --rc4, --appelbaum        which RC4 ciphers are being offered?

tuning / connect options (most also can be preset via environment variables):
     --fast                        omits some checks: using openssl for all ciphers (-e), show only first preferred cipher.
     -9, --full                    includes tests for implementation bugs and cipher per protocol (could disappear)
     --bugs                        enables the "-bugs" option of s_client, needed e.g. for some buggy F5s
     --assume-http                 if protocol check fails it assumes HTTP protocol and enforces HTTP checks
     --ssl-native                  fallback to checks with OpenSSL where sockets are normally used
     --openssl <PATH>              use this openssl binary (default: look in \$PATH, \$RUN_DIR of $PROG_NAME)
     --proxy <host:port|auto>      (experimental) proxy connects via <host:port>, auto: values from \$env (\$http(s)_proxy)
     -6                            also use IPv6. Works only with supporting OpenSSL version and IPv6 connectivity
     --ip <ip>                     a) tests the supplied <ip> v4 or v6 address instead of resolving host(s) in URI
                                   b) arg "one" means: just test the first DNS returns (useful for multiple IPs)
     -n, --nodns <min|none>        if "none": do not try any DNS lookups, "min" queries A, AAAA and MX records
     --sneaky                      leave less traces in target logs: user agent, referer
     --ids-friendly                skips a few vulnerability checks which may cause IDSs to block the scanning IP
     --phone-out                   allow to contact external servers for CRL download and querying OCSP responder
     --add-ca <cafile>             path to <cafile> or a comma separated list of CA files enables test against additional CAs.
     --basicauth <user:pass>       provide HTTP basic auth information.

output options (can also be preset via environment variables):
     --quiet                       don't output the banner. By doing this you acknowledge usage terms normally appearing in the banner
     --wide                        wide output for tests like RC4, BEAST. FS also with hexcode, kx, strength, RFC name
     --show-each                   for wide outputs: display all ciphers tested -- not only succeeded ones
     --mapping <openssl|           openssl: use the OpenSSL cipher suite name as the primary name cipher suite name form (default)
                iana|rfc             -> use the IANA/(RFC) cipher suite name as the primary name cipher suite name form
                no-openssl|          -> don't display the OpenSSL cipher suite name, display IANA/(RFC) names only
                no-iana|no-rfc>      -> don't display the IANA/(RFC) cipher suite name, display OpenSSL names only
     --color <0|1|2|3>             0: no escape or other codes,  1: b/w escape codes,  2: color (default), 3: extra color (color all ciphers)
     --colorblind                  swap green and blue in the output
     --debug <0-6>                 1: screen output normal but keeps debug output in /tmp/.  2-6: see "grep -A 5 '^DEBUG=' testssl.sh"
     --disable-rating              Explicitly disables the rating output

file output options (can also be preset via environment variables)
     --log, --logging              logs stdout to '\${NODE}-p\${port}\${YYYYMMDD-HHMM}.log' in current working directory (cwd)
     --logfile|-oL <logfile>       logs stdout to 'dir/\${NODE}-p\${port}\${YYYYMMDD-HHMM}.log'. If 'logfile' is a dir or to a specified 'logfile'
     --json                        additional output of findings to flat JSON file '\${NODE}-p\${port}\${YYYYMMDD-HHMM}.json' in cwd
     --jsonfile|-oj <jsonfile>     additional output to the specified flat JSON file or directory, similar to --logfile
     --json-pretty                 additional JSON structured output of findings to a file '\${NODE}-p\${port}\${YYYYMMDD-HHMM}.json' in cwd
     --jsonfile-pretty|-oJ <jsonfile>  additional JSON structured output to the specified file or directory, similar to --logfile
     --csv                         additional output of findings to CSV file '\${NODE}-p\${port}\${YYYYMMDD-HHMM}.csv' in cwd or directory
     --csvfile|-oC <csvfile>       additional output as CSV to the specified file or directory, similar to --logfile
     --html                        additional output as HTML to file '\${NODE}-p\${port}\${YYYYMMDD-HHMM}.html'
     --htmlfile|-oH <htmlfile>     additional output as HTML to the specified file or directory, similar to --logfile
     --out(f,F)ile|-oa/-oA <fname> log to a LOG,JSON,CSV,HTML file (see nmap). -oA/-oa: pretty/flat JSON.
                                   "auto" uses '\${NODE}-p\${port}\${YYYYMMDD-HHMM}'. If fname if a dir uses 'dir/\${NODE}-p\${port}\${YYYYMMDD-HHMM}'
     --hints                       additional hints to findings
     --severity <severity>         severities with lower level will be filtered for CSV+JSON, possible values <LOW|MEDIUM|HIGH|CRITICAL>
     --append                      if (non-empty) <logfile>, <csvfile>, <jsonfile> or <htmlfile> exists, append to file. Omits any header
     --outprefix <fname_prefix>    before  '\${NODE}.' above prepend <fname_prefix>


Options requiring a value can also be called with '=' e.g. testssl.sh -t=smtp --wide --openssl=/usr/bin/openssl <URI>.
<URI> always needs to be the last parameter.

EOF
     # Set HTMLHEADER and JSONHEADER to false so that the cleanup() function won't
     # try to write footers to the HTML and JSON files.
     HTMLHEADER=false
     JSONHEADER=false
     #' Fix syntax highlight on sublime
     "$CHILD_MASS_TESTING" && kill -s USR1 $PPID
     exit $1
}

maketempf() {
     TEMPDIR=$(mktemp -d /tmp/testssl.XXXXXX 2>/dev/null)
     if [[ $? -ne 0 ]]; then
          # For e.g. devices where we can't write to /tmp we chose $PWD but we can't
          # allow every char as we haven't quoted all strings depending on it, see #1445
          if [[ $PWD =~ [^A-Za-z0-9\.,/_-] ]]; then
               fatal "\$PWD contains illegal chars: \"$BASH_REMATCH\"" $ERR_FCREATE
          fi
          TEMPDIR=$(mktemp -d "$PWD/testssl.XXXXXX") || exit $ERR_FCREATE
     fi
     TMPFILE=$TEMPDIR/tempfile.txt || exit $ERR_FCREATE
     if [[ "$DEBUG" -eq 0 ]]; then
          ERRFILE="/dev/null"
     else
          ERRFILE=$TEMPDIR/errorfile.txt || exit $ERR_FCREATE
     fi
     HOSTCERT=$TEMPDIR/host_certificate.pem
}

prepare_debug() {
     if [[ $DEBUG -ne 0 ]]; then
          cat >$TEMPDIR/environment.txt << EOF


CVS_REL: $CVS_REL
GIT_REL: $GIT_REL

PID: $$
commandline: "$CMDLINE"
bash version: ${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}.${BASH_VERSINFO[2]}
status: ${BASH_VERSINFO[4]}
machine: ${BASH_VERSINFO[5]}
operating system: $SYSTEM $SYSTEMREV
os constraint: $SYSTEM2
shellopts: $SHELLOPTS
printf: $PRINTF
NO_ITALICS: $NO_ITALICS

$($OPENSSL version -a 2>/dev/null)
OSSL_VER_MAJOR: $OSSL_VER_MAJOR
OSSL_VER_MINOR: $OSSL_VER_MINOR
OSSL_VER_APPENDIX: $OSSL_VER_APPENDIX
OSSL_BUILD_DATE: $OSSL_BUILD_DATE
OSSL_VER_PLATFORM: $OSSL_VER_PLATFORM

OPENSSL_NR_CIPHERS: $OPENSSL_NR_CIPHERS
OPENSSL_CONF: $OPENSSL_CONF
HAS_CURVES: $HAS_CURVES
OSSL_SUPPORTED_CURVES: $OSSL_SUPPORTED_CURVES

HAS_IPv6: $HAS_IPv6
HAS_SSL2: $HAS_SSL2
HAS_SSL3: $HAS_SSL3
HAS_TLS13: $HAS_TLS13
HAS_X448: $HAS_X448
HAS_X25519: $HAS_X25519
HAS_SIGALGS: $HAS_SIGALGS
HAS_NO_SSL2: $HAS_NO_SSL2
HAS_SPDY: $HAS_SPDY
HAS_ALPN: $HAS_ALPN
HAS_FALLBACK_SCSV: $HAS_FALLBACK_SCSV
HAS_COMP: $HAS_COMP
HAS_NO_COMP: $HAS_NO_COMP
HAS_CIPHERSUITES: $HAS_CIPHERSUITES
HAS_SECLEVEL: $HAS_SECLEVEL
HAS_PKEY: $HAS_PKEY
HAS_PKUTIL: $HAS_PKUTIL
HAS_PROXY: $HAS_PROXY
HAS_XMPP: $HAS_XMPP
HAS_XMPP_SERVER: $HAS_XMPP_SERVER
HAS_POSTGRES: $HAS_POSTGRES
HAS_MYSQL: $HAS_MYSQL
HAS_LMTP: $HAS_LMTP
HAS_NNTP: $HAS_NNTP
HAS_IRC: $HAS_IRC

HAS_DIG: $HAS_DIG
HAS_HOST: $HAS_HOST
HAS_DRILL: $HAS_DRILL
HAS_NSLOOKUP: $HAS_NSLOOKUP
HAS_IDN: $HAS_IDN
HAS_IDN2: $HAS_IDN2
HAS_AVAHIRESOLVE: $HAS_AVAHIRESOLVE
HAS_DIG_NOIDNOUT: $HAS_DIG_NOIDNOUT

PATH: $PATH
PROG_NAME: $PROG_NAME
TESTSSL_INSTALL_DIR: $TESTSSL_INSTALL_DIR
RUN_DIR: $RUN_DIR
CIPHERS_BY_STRENGTH_FILE: $CIPHERS_BY_STRENGTH_FILE

CAPATH: $CAPATH
COLOR: $COLOR
COLORBLIND: $COLORBLIND
TERM_WIDTH: $TERM_WIDTH
INTERACTIVE: $INTERACTIVE
HAS_GNUDATE: $HAS_GNUDATE
HAS_FREEBSDDATE: $HAS_FREEBSDDATE
HAS_OPENBSDDATE: $HAS_OPENBSDDATE
HAS_SED_E: $HAS_SED_E

SHOW_EACH_C: $SHOW_EACH_C
SSL_NATIVE: $SSL_NATIVE
ASSUME_HTTP $ASSUME_HTTP
BASICAUTH: $BASICAUTH
SNEAKY: $SNEAKY
OFFENSIVE: $OFFENSIVE
PHONE_OUT: $PHONE_OUT

DEBUG: $DEBUG

HSTS_MIN: $HSTS_MIN
HPKP_MIN: $HPKP_MIN
CLIENT_MIN_FS: $CLIENT_MIN_FS
DAYS2WARN1: $DAYS2WARN1
DAYS2WARN2: $DAYS2WARN2

HEADER_MAXSLEEP: $HEADER_MAXSLEEP
MAX_WAITSOCK: $MAX_WAITSOCK
HEARTBLEED_MAX_WAITSOCK: $HEARTBLEED_MAX_WAITSOCK
CCS_MAX_WAITSOCK: $CCS_MAX_WAITSOCK
USLEEP_SND $USLEEP_SND
USLEEP_REC $USLEEP_REC

EOF
          type -p locale &>/dev/null && locale >>$TEMPDIR/environment.txt || echo "locale doesn't exist" >>$TEMPDIR/environment.txt
          actually_supported_osslciphers 'ALL:COMPLEMENTOFALL' 'ALL' "-V" &>$TEMPDIR/all_local_ciphers.txt
     fi
     # see also $TEMPDIR/s_client_has.txt from find_openssl_binary
}


prepare_arrays() {
     local hexc mac ossl_ciph
     local ossl_supported_tls="" ossl_supported_sslv2=""
     local -i i=0

     if [[ -e "$CIPHERS_BY_STRENGTH_FILE" ]]; then
          "$HAS_SSL2" && ossl_supported_sslv2="$($OPENSSL ciphers -ssl2 -V 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>$ERRFILE)"
          if "$HAS_SSL2"; then
               ossl_supported_tls="$(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 'ALL' "-tls1 -V")"
          else
               ossl_supported_tls="$(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 'ALL' "-V")"
          fi
          TLS13_OSSL_CIPHERS=""
          while read hexc n TLS_CIPHER_OSSL_NAME[i] TLS_CIPHER_RFC_NAME[i] TLS_CIPHER_SSLVERS[i] TLS_CIPHER_KX[i] TLS_CIPHER_AUTH[i] TLS_CIPHER_ENC[i] mac TLS_CIPHER_EXPORT[i]; do
               TLS_CIPHER_HEXCODE[i]="$hexc"
               TLS_CIPHER_OSSL_SUPPORTED[i]=false
               if [[ ${#hexc} -eq 9 ]]; then
                    # >= SSLv3 ciphers
                    if [[ $OSSL_VER_MAJOR -lt 1 ]]; then
                         [[ ":${ossl_supported_tls}:" =~ ":${TLS_CIPHER_OSSL_NAME[i]}:" ]] && TLS_CIPHER_OSSL_SUPPORTED[i]=true
                    else
                         ossl_ciph="$(awk '/'"$hexc"'/ { print $3 }' <<< "$ossl_supported_tls")"
                         if [[ -n "$ossl_ciph" ]]; then
                              TLS_CIPHER_OSSL_SUPPORTED[i]=true
                              [[ "$ossl_ciph" != ${TLS_CIPHER_OSSL_NAME[i]} ]] && TLS_CIPHER_OSSL_NAME[i]="$ossl_ciph"
                              [[ "${hexc:2:2}" == 13 ]] && TLS13_OSSL_CIPHERS+=":$ossl_ciph"
                         fi
                    fi
               elif [[ $OSSL_VER_MAJOR -lt 1 ]]; then
                    [[ ":${ossl_supported_sslv2}:" =~ ":${TLS_CIPHER_OSSL_NAME[i]}:" ]] && TLS_CIPHER_OSSL_SUPPORTED[i]=true
               else
                    [[ "$ossl_supported_sslv2" =~ $hexc ]] && TLS_CIPHER_OSSL_SUPPORTED[i]=true
               fi
               i+=1
          done < "$CIPHERS_BY_STRENGTH_FILE"
     fi
     TLS_NR_CIPHERS=i
     TLS13_OSSL_CIPHERS="${TLS13_OSSL_CIPHERS:1}"
}


mybanner() {
     local idtag
     local bb1 bb2 bb3

     "$QUIET" && return
     "$CHILD_MASS_TESTING" && return
     OPENSSL_NR_CIPHERS=$(count_ciphers "$(actually_supported_osslciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 'ALL')")
     [[ -z "$GIT_REL" ]] && \
          idtag="$CVS_REL" || \
          idtag="$GIT_REL -- $CVS_REL_SHORT"
     bb1=$(cat <<EOF

###########################################################
    $PROG_NAME       $VERSION from
EOF
)
     bb2=$(cat <<EOF

      This program is free software. Distribution and
             modification under GPLv2 permitted.
      USAGE w/o ANY WARRANTY. USE IT AT YOUR OWN RISK!

       Please file bugs @
EOF
)
     bb3=$(cat <<EOF

###########################################################
EOF
)
     pr_bold "$bb1 "
     pr_boldurl "$SWURL"; outln
     if [[ -n "$idtag" ]]; then
          #FIXME: if we run it not off the git dir we miss the version tag.
          # at least we don't want to display empty brackets here...
          pr_bold "    ("
          pr_grey "$idtag"
          prln_bold ")"
     fi
     pr_bold "$bb2 "
     pr_boldurl "https://testssl.sh/bugs/"; outln
     pr_bold "$bb3"
     outln "\n"
     outln " Using \"$($OPENSSL version 2>/dev/null)\" [~$OPENSSL_NR_CIPHERS ciphers]"
     out " on $HNAME:"
     outln "$OPENSSL_LOCATION"
     outln " (built: \"$OSSL_BUILD_DATE\", platform: \"$OSSL_VER_PLATFORM\")\n"
}

calc_scantime() {
          END_TIME=$(date +%s)
          SCAN_TIME=$(( END_TIME - START_TIME ))
}

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.
     "$INTERACTIVE" && [[ $NR_PARALLEL_TESTS -gt 0 ]] && echo -en "\r                                                             \r" 1>&2
     while [[ $NEXT_PARALLEL_TEST_TO_FINISH -lt $NR_PARALLEL_TESTS ]]; do
          if [[ ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} -ne 0 ]] && \
             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)
               get_next_message_testing_parallel_result "stopped"
          else
               # If a test had already completed, but its output wasn't yet processed,
               # then process it now.
               get_next_message_testing_parallel_result "completed"
          fi
          NEXT_PARALLEL_TEST_TO_FINISH+=1
     done
     if [[ "$DEBUG" -ge 1 ]]; then
          tmln_out
          tm_underline "DEBUG (level $DEBUG): see files in $TEMPDIR"
          tmln_out
     else
          [[ -d "$TEMPDIR" ]] && rm -rf "$TEMPDIR";
     fi
     outln
     # No shorthand expression to avoid errors when $CMDLINE_PARSED haven't been filled yet.
     if [[ $CMDLINE_PARSED == true ]]; then
          "$SECTION_FOOTER_NEEDED" && fileout_section_footer true
          html_footer
          fileout_footer
     fi
     # debugging off, see above
     grep -q xtrace <<< "$SHELLOPTS" && ! "$DEBUG_ALLINONE" && exec 2>&42 42>&-
}

# see https://unix.stackexchange.com/questions/57940/trap-int-term-exit-really-necessary
sig_cleanup() {
     trap '' EXIT
     cleanup
     exit 0
}

child_error() {
     cleanup
     exit $ERR_CHILD
}


# Program terminates prematurely, with error code
# arg1: string to print / to write to file
# arg2: global error code, see ERR_* above
# arg3: an optional hint (string)
#
fatal() {
     outln
     prln_magenta "Fatal error: $1" >&2
     [[ -n "$LOGFILE" ]] && prln_magenta "Fatal error: $1" >>$LOGFILE
     if [[ -n "$3" ]]; then
          outln "$3" >&2
          [[ -n "$LOGFILE" ]] && outln "$3" >>$LOGFILE
     fi
     # Make sure we don't try to write into files when not created yet.
     # No shorthand expression to avoid errors when $CMDLINE_PARSED haven't been filled yet.
     [[ $CMDLINE_PARSED == true ]] && fileout "scanProblem" "FATAL" "$1"
     exit $2
}

# This OTOH doesn't exit but puts a fatal error to the screen but continues with the next
# IP/hostname. It should only be used if a single IP/Hostname in a scan is not reachable.
# arg1: string to print / to write to file
#
ip_fatal() {
     outln
     prln_magenta "Fatal error: $1, proceeding with next IP (if any)" >&2
     [[ -n "$LOGFILE" ]] && prln_magenta "Fatal error: $1, proceeding with next IP (if any)" >>$LOGFILE
     outln
     fileout "scanProblem" "FATAL" "$1, proceeding with next IP (if any)"
     return 0
}

# This generic function outputs an error onto the screen and handles logging.
# arg1: string to print / to write to file, arg2 (optional): additional hint to write
#
generic_nonfatal() {
     prln_magenta "$1" >&2
     [[ -n $2 ]] && outln "$2"
     [[ -n "$LOGFILE" ]] && prln_magenta "$1" >>$LOGFILE && [[ -n $2 ]] && outln "$2" >>$LOGFILE
     outln
     fileout "scanProblem" "WARN" "$1"
     return 0
}

initialize_engine(){
     # for now only GOST engine
     grep -q '^# testssl config file' "$OPENSSL_CONF" 2>/dev/null && \
          return 0        # We have been here already
     if "$NO_ENGINE"; then
          # Avoid potential conflicts also -- manual hook, see #1117
          export OPENSSL_CONF=''
          return 1
     elif $OPENSSL engine gost -v 2>&1 | grep -Eq 'invalid command|no such engine'; then
          outln
          pr_warning "No engine or GOST support via engine with your $OPENSSL"; outln
          fileout_insert_warning "engine_problem" "WARN" "No engine or GOST support via engine with your $OPENSSL"
          export OPENSSL_CONF=''
          return 1
     elif ! $OPENSSL engine gost -vvvv -t -c 2>/dev/null >/dev/null; then
          # check for openssl 1.1.1 config -- not this may not be reliable. We only use this
          # to suppress the warning (confuses users), see #1119
          # https://github.com/openssl/openssl/commit/b524b808a1d1ba204dbdcbb42de4e3bddb3472ac
          if ! grep -q 'using the .include directive' /etc/ssl/openssl.cnf; then
               outln
               pr_warning "No engine or GOST support via engine with your $OPENSSL"; outln
          fi
          fileout_insert_warning "engine_problem" "WARN" "No engine or GOST support via engine with your $OPENSSL"
          # Avoid clashes of OpenSSL 1.1.1 config file with our openssl 1.0.2. This is for Debian 10
          export OPENSSL_CONF=''
          return 1
     else      # we have engine support
          if [[ -n "$OPENSSL_CONF" ]]; then
               prln_warning "For now I am providing the config file to have GOST support"
          else
               OPENSSL_CONF=$TEMPDIR/gost.conf
               # see https://www.mail-archive.com/openssl-users@openssl.org/msg65395.html
               cat >$OPENSSL_CONF << EOF
# testssl config file for openssl

openssl_conf            = openssl_def

[ openssl_def ]
engines                 = engine_section

[ engine_section ]
gost = gost_section

[ gost_section ]
engine_id = gost
default_algorithms = ALL
CRYPT_PARAMS = id-Gost28147-89-CryptoPro-A-ParamSet

EOF
               [[ $? -ne 0 ]] && exit $ERR_OSSLBIN
               export OPENSSL_CONF
          fi
     fi
     return 0
}

# arg1: text to display before "-->"
# arg2: arg needed to accept to continue
ignore_no_or_lame() {
     local a

     [[ "$WARNINGS" == off ]] && return 0
     [[ "$WARNINGS" == batch ]] && return 1
     tm_warning "$1 --> "
     read a
     if [[ "$2" == "$(toupper "$2")" ]]; then
          # all uppercase requested
          if [[ "$a" == "$2" ]];  then
               return 0
          else
               return 1
          fi
     elif [[ "$2" == "$(tolower "$a")" ]]; then
          # we normalize the word to continue
          return 0
     else
          return 1
     fi
}

# arg1: URI
parse_hn_port() {
     local tmp_port
     local node_tmp=""

     NODE="$1"
     NODE="${NODE/https\:\/\//}"        # strip "https"
     NODE="${NODE%%/*}"                 # strip trailing urlpath
     NODE="${NODE%%.}"                  # strip trailing "." if supplied
     if grep -q ':$' <<< "$NODE"; then
          if grep -wq http <<< "$NODE"; then
               fatal "\"http\" is not what you meant probably" $ERR_CMDLINE
          else
               fatal "\"$1\" is not a valid URI" $ERR_CMDLINE
          fi
     fi
     # Was an IPv6 address supplied like [AA:BB:CC::]:port ?
     if grep -q ']' <<< "$NODE"; then
          tmp_port=$(printf "$NODE" | sed 's/\[.*\]//' | sed 's/://')
          # determine v6 port, supposed it was supplied additionally
          if [[ -n "$tmp_port" ]]; then
               PORT=$tmp_port
               NODE=$(sed "s/:$PORT//" <<< "$NODE")
          fi
          NODE=$(sed -e 's/\[//' -e 's/\]//' <<< "$NODE")
     else
          # determine v4 port, supposed it was supplied additionally
          grep -q ':' <<< "$NODE" && \
               PORT=$(sed 's/^.*\://' <<< "$NODE") && NODE=$(sed 's/\:.*$//' <<< "$NODE")
     fi

     # We check for non-ASCII chars now. If there are some we'll try to convert it if IDN/IDN2 is installed
     # If not, we'll continue. Hoping later that dig can use it. If not the error handler will tell
     # Honestly we don't care whether it's IDN2008 or IDN2003 or Emoji domains as long as it works.
     # So we try to resolve anything supplied. If it can't  our resolver error handler takes care
     if [[ "$NODE" == *[![:ascii:]]* ]]; then
          if ! "$HAS_IDN2" && ! "$HAS_IDN"; then
               prln_warning " URI contains non-ASCII characters and libidn/libidn2 not available."
               outln " Trying to feed the resolver without converted \"$NODE\" ...\n"
               #ToDo: fileout is missing
               node_tmp="$NODE"
          elif "$HAS_IDN2"; then
               node_tmp="$(idn2 "$NODE" 2>/dev/null)"
          fi
          if "$HAS_IDN" && [[ -z "$node_tmp" ]]; then
               node_tmp="$(idn "$NODE" 2>/dev/null)"
          fi
          if [[ -z "$node_tmp" ]]; then
               prln_warning " URI contains non-ASCII characters and IDN conversion failed."
               outln " Trying to feed the resolver without converted \"$NODE\" ...\n"
               #ToDo: fileout is missing
               node_tmp="$NODE"
          fi
          NODE="$node_tmp"
     fi

     debugme echo $NODE:$PORT
     SNI="-servername $NODE"
     URL_PATH=$(sed 's/https:\/\///' <<< "$1" | sed 's/'"${NODE}"'//' | sed 's/.*'"${PORT}"'//')      # remove protocol and node part and port
     URL_PATH=$(sed 's/\/\//\//g' <<< "$URL_PATH")          # we rather want // -> /
     URL_PATH=${URL_PATH%%.}                                # strip trailing "." so that it is not interpreted as URL
     [[ -z "$URL_PATH" ]] && URL_PATH="/"
     debugme echo "URL_PATH: $URL_PATH"
     return 0                                               # NODE, URL_PATH, PORT is set now
}


# args: string containing ip addresses
filter_ip6_address() {
     local a

     for a in "$@"; do
          if ! is_ipv6addr "$a"; then
               continue
          fi
          if "$HAS_SED_E"; then
               sed -E 's/^abcdeABCDEFf0123456789:]//g' <<< "$a" | sed -e '/^$/d' -e '/^;;/d'
          else
               sed -r 's/[^abcdefABCDEF0123456789:]//g' <<< "$a" | sed -e '/^$/d' -e '/^;;/d'
          fi
     done
}

filter_ip4_address() {
     local a

     for a in "$@"; do
          if ! is_ipv4addr "$a"; then
               continue
          fi
          if "$HAS_SED_E"; then
               sed -E 's/[^[:digit:].]//g' <<< "$a" | sed -e '/^$/d'
          else
               sed -r 's/[^[:digit:].]//g' <<< "$a" | sed -e '/^$/d'
          fi
     done
}

# For security testing sometimes we have local entries. Getent is BS under Linux for localhost: No network, no resolution
# arg1 is the entry we want to look up in the host file
get_local_aaaa() {
     local ip6=""
     local etchosts="/etc/hosts /c/Windows/System32/drivers/etc/hosts"

     [[ -z "$1" ]] && echo "" && return 1
     # Also multiple records should work fine
     ip6=$(grep -wih "$1" $etchosts 2>/dev/null | grep ':' | grep -Ev '^#|\.local' | grep -Ei "[[:space:]]$1" | awk '{ print $1 }')
     if is_ipv6addr "$ip6"; then
          echo "$ip6"
     else
          echo ""
     fi
}
get_local_a() {
     local ip4=""
     local etchosts="/etc/hosts /c/Windows/System32/drivers/etc/hosts"

     ip4=$(grep -wih "$1" $etchosts 2>/dev/null | grep -Ev ':|^#|\.local' | grep -Ei "[[:space:]]$1" | awk '{ print $1 }')
     if is_ipv4addr "$ip4"; then
          echo "$ip4"
     else
          echo ""
     fi
}

# Does a hard exit if no lookup binary is provided
# Checks for IDN capabilities also
#
check_resolver_bins() {
     local saved_openssl_conf="$OPENSSL_CONF"

     type -p dig   &> /dev/null &&  HAS_DIG=true
     type -p host  &> /dev/null &&  HAS_HOST=true
     type -p drill &> /dev/null &&  HAS_DRILL=true
     type -p nslookup &> /dev/null && HAS_NSLOOKUP=true
     type -p avahi-resolve &>/dev/null && HAS_AVAHIRESOLVE=true
     type -p idn  &>/dev/null && HAS_IDN=true
     type -p idn2 &>/dev/null && HAS_IDN2=true

     OPENSSL_CONF=""                         # see https://github.com/drwetter/testssl.sh/issues/134
     if ! "$HAS_DIG" && ! "$HAS_HOST" && ! "$HAS_DRILL" && ! "$HAS_NSLOOKUP"; then
          fatal "Neither \"dig\", \"host\", \"drill\" or \"nslookup\" is present" $ERR_DNSBIN
     fi
     if "$HAS_DIG"; then
          if dig +noidnout -t a 2>&1 | grep -Eq 'Invalid option: \+noidnout|IDN support not enabled'; then
               :
          else
               HAS_DIG_NOIDNOUT=true
          fi
     fi
     OPENSSL_CONF="$saved_openssl_conf"      # see https://github.com/drwetter/testssl.sh/issues/134
     return 0
}

# arg1: a host name. Returned will be 0-n IPv4 addresses
# watch out: $1 can also be a cname! --> all checked
get_a_record() {
     local ip4=""
     local saved_openssl_conf="$OPENSSL_CONF"
     local noidnout=""

     "$HAS_DIG_NOIDNOUT" && noidnout="+noidnout"
     [[ "$NODNS" == none ]] && return 0      # if no DNS lookup was instructed, leave here
     if [[ "$1" == localhost ]]; then
          # This is a bit ugly but prevents from doing DNS lookups which could fail
          echo 127.0.0.1
          return 0
     fi
     if is_ipv4addr "$1"; then
          # This saves walking through this. Also it avoids hangs e.g. if you run docker locally without reachabale DNS
          echo $1
          return 0
     fi
     OPENSSL_CONF=""                         # see https://github.com/drwetter/testssl.sh/issues/134
     if [[ "$NODE" == *.local ]]; then
          if "$HAS_AVAHIRESOLVE"; then
               ip4=$(filter_ip4_address $(avahi-resolve -4 -n "$1" 2>/dev/null | awk '{ print $2 }'))
          elif "$HAS_DIG"; then
               ip4=$(filter_ip4_address $(dig @224.0.0.251 -p 5353 +short -t a +notcp "$1" 2>/dev/null | sed '/^;;/d'))
          else
               fatal "Local hostname given but no 'avahi-resolve' or 'dig' available." $ERR_DNSBIN
          fi
     fi
     if [[ -z "$ip4" ]] && "$HAS_DIG"; then
          ip4=$(filter_ip4_address $(dig +short +timeout=2 +tries=2 $noidnout -t a "$1" 2>/dev/null | awk '/^[0-9]/ { print $1 }'))
     fi
     if [[ -z "$ip4" ]] && "$HAS_HOST"; then
          ip4=$(filter_ip4_address $(host -t a "$1" 2>/dev/null | awk '/address/ { print $NF }'))
     fi
     if [[ -z "$ip4" ]] && "$HAS_DRILL"; then
          ip4=$(filter_ip4_address $(drill a "$1" | awk '/ANSWER SECTION/,/AUTHORITY SECTION/ { print $NF }' | awk '/^[0-9]/'))
     fi
     if [[ -z "$ip4" ]] && "$HAS_NSLOOKUP"; then
          ip4=$(filter_ip4_address $(strip_lf "$(nslookup -querytype=a "$1" 2>/dev/null | awk '/^Name/ { getline; print $NF }')"))
     fi
     OPENSSL_CONF="$saved_openssl_conf"      # see https://github.com/drwetter/testssl.sh/issues/134
     echo "$ip4"
}

# arg1: a host name. Returned will be 0-n IPv6 addresses
# watch out: $1 can also be a cname! --> all checked
get_aaaa_record() {
     local ip6=""
     local saved_openssl_conf="$OPENSSL_CONF"
     local noidnout=""

     "$HAS_DIG_NOIDNOUT" && noidnout="+noidnout"
     [[ "$NODNS" == none ]] && return 0      # if no DNS lookup was instructed, leave here
     OPENSSL_CONF=""                         # see https://github.com/drwetter/testssl.sh/issues/134
     if is_ipv6addr "$1"; then
          # This saves walking through this. Also it avoids hangs e.g. if you run docker locally without reachabale DNS
          echo "$1"
          return 0
     elif is_ipv4addr "$1"; then
          # we need also this here as get_aaaa_record is always called after get_a_record and we want to handle this at a low level
          return 0
     fi
     if [[ -z "$ip6" ]]; then
          if [[ "$NODE" == *.local ]]; then
               if "$HAS_AVAHIRESOLVE"; then
                    ip6=$(filter_ip6_address $(avahi-resolve -6 -n "$1" 2>/dev/null | awk '{ print $2 }'))
               elif "$HAS_DIG"; then
                    ip6=$(filter_ip6_address $(dig @ff02::fb -p 5353 -t aaaa +short +notcp "$NODE"))
               else
                    fatal "Local hostname given but no 'avahi-resolve' or 'dig' available." $ERR_DNSBIN
               fi
          elif "$HAS_DIG"; then
               ip6=$(filter_ip6_address $(dig +short +timeout=2 +tries=2 $noidnout -t aaaa "$1" 2>/dev/null | awk '/^[0-9]/ { print $1 }'))
          elif "$HAS_HOST"; then
               ip6=$(filter_ip6_address $(host -t aaaa "$1" | awk '/address/ { print $NF }'))
          elif "$HAS_DRILL"; then
               ip6=$(filter_ip6_address $(drill aaaa "$1" | awk '/ANSWER SECTION/,/AUTHORITY SECTION/ { print $NF }' | awk '/^[0-9]/'))
          elif "$HAS_NSLOOKUP"; then
               ip6=$(filter_ip6_address $(strip_lf "$(nslookup -type=aaaa "$1" 2>/dev/null | awk '/'"^${a}"'.*AAAA/ { print $NF }')"))
          fi
     fi
     OPENSSL_CONF="$saved_openssl_conf"      # see https://github.com/drwetter/testssl.sh/issues/134
     echo "$ip6"
}

# RFC6844: DNS Certification Authority Authorization (CAA) Resource Record
# arg1: domain to check for
get_caa_rr_record() {
     local raw_caa=""
     local -i len_caa_property
     local caa_property_name
     local caa_property_value
     local saved_openssl_conf="$OPENSSL_CONF"
     local all_caa=""
     local noidnout=""

     "$HAS_DIG_NOIDNOUT" && noidnout="+noidnout"

     [[ -n "$NODNS" ]] && return 0           # if minimum DNS lookup was instructed, leave here
     # if there's a type257 record there are two output formats here, mostly depending on age of distribution
     # roughly that's the difference between text and binary format
     # 1) 'google.com has CAA record 0 issue "symantec.com"'
     # 2) 'google.com has TYPE257 record \# 19 0005697373756573796D616E7465632E636F6D'
     # for dig +short the output always starts with '0 issue [..]' or '\# 19 [..]' so we normalize thereto to keep caa_flag, caa_property
     # caa_property then has key/value pairs, see https://tools.ietf.org/html/rfc6844#section-3
     OPENSSL_CONF=""
     if "$HAS_DIG"; then
          raw_caa="$(dig +short +timeout=3 +tries=3 $noidnout type257 "$1" 2>/dev/null | awk '{ print $1" "$2" "$3 }')"
          # empty if no CAA record
     elif "$HAS_DRILL"; then
          raw_caa="$(drill $1 type257 | awk '/'"^${1}"'.*CAA/ { print $5,$6,$7 }')"
     elif "$HAS_HOST"; then
          raw_caa="$(host -t type257 $1)"
          if grep -Ewvq "has no CAA|has no TYPE257" <<< "$raw_caa"; then
               raw_caa="$(sed -e 's/^.*has CAA record //' -e 's/^.*has TYPE257 record //' <<< "$raw_caa")"
          fi
     elif "$HAS_NSLOOKUP"; then
          raw_caa="$(strip_lf "$(nslookup -type=type257 $1 | grep -w rdata_257)")"
          if [[ -n "$raw_caa" ]]; then
               raw_caa="$(sed 's/^.*rdata_257 = //' <<< "$raw_caa")"
          fi
     else
          return 1
          # No dig, drill, host, or nslookup --> complaint was elsewhere already
     fi
     OPENSSL_CONF="$saved_openssl_conf"      # see https://github.com/drwetter/testssl.sh/issues/134
     debugme echo $raw_caa

     if [[ "$raw_caa" =~ \#\ [0-9][0-9] ]]; then
          # for posteo we get this binary format returned e.g. for old dig versions:
          # \# 19 0005697373756567656F74727573742E636F6D
          # \# 23 0009697373756577696C6467656F74727573742E636F6D
          # \# 34 0005696F6465666D61696C746F3A686F73746D617374657240706F73 74656F2E6465
          #  # len caaflag <more_see_below>                       @ p o s  t e o . d e
          while read hash len line ;do
               if [[ "${line:0:2}" == "00" ]]; then                             # probably the caa flag, always 00, so we don't keep this
                    len_caa_property=$(printf "%0d" "$((10#${line:2:2}))")      # get len and do type casting, for posteo we have 05 or 09 here as a string
                    len_caa_property=$((len_caa_property*2))                    # =>word! Now get name from 4th and value from 4th+len position...
                    line="${line/ /}"                                           # especially with iodefs there's a blank in the string which we just skip
                    caa_property_name="$(hex2ascii ${line:4:$len_caa_property})"
                    caa_property_value="$(hex2ascii "${line:$((4+len_caa_property)):100}")"
                    # echo "${caa_property_name}=${caa_property_value}"
                    all_caa+="${caa_property_name}=${caa_property_value}\n"
               else
                    outln "please report unknown CAA RR $line with flag  @ $NODE"
                    return 7
               fi
          done <<< "$raw_caa"
          sort <<< "$(safe_echo "$all_caa")"
          return 0
     elif grep -q '"' <<< "$raw_caa"; then
          raw_caa=${raw_caa//\"/}                           # strip all ". Now we should have flag, name, value
          #caa_property_name="$(awk '{ print $2 }' <<< "$raw_caa")"
          #caa_property_value="$(awk '{ print $3 }' <<< "$raw_caa")"
          safe_echo "$(sort <<< "$(awk '{ print $2"="$3 }' <<< "$raw_caa")")"
          return 0
     else
          # no caa record
          return 1
     fi

# to do:
#    4: check whether $1 is a CNAME and take this
     return 0
}

# watch out: $1 can also be a cname! --> all checked
get_mx_record() {
     local mx=""
     local saved_openssl_conf="$OPENSSL_CONF"
     local noidnout=""

     "$HAS_DIG_NOIDNOUT" && noidnout="+noidnout"
     OPENSSL_CONF=""                         # see https://github.com/drwetter/testssl.sh/issues/134
     # we need the last two columns here
     if "$HAS_HOST"; then
          mxs="$(host -t MX "$1" 2>/dev/null | awk '/is handled by/ { print $(NF-1), $NF }')"
     elif "$HAS_DIG"; then
          mxs="$(dig +short $noidnout -t MX "$1" 2>/dev/null | awk '/^[0-9]/ { print $1" "$2 }')"
     elif "$HAS_DRILL"; then
          mxs="$(drill mx $1 | awk '/IN[ \t]MX[ \t]+/ { print $(NF-1), $NF }')"
     elif "$HAS_NSLOOKUP"; then
          mxs="$(strip_lf "$(nslookup -type=MX "$1" 2>/dev/null | awk '/mail exchanger/ { print $(NF-1), $NF }')")"
     else
          # shouldn't reach this, as we checked in the top
          fatal "No dig, host, drill or nslookup" $ERR_DNSBIN
     fi
     OPENSSL_CONF="$saved_openssl_conf"
     echo "$mxs"
}


# set IPADDRs and IP46ADDRs
#
determine_ip_addresses() {
     local ip4=""
     local ip6=""

     ip4="$(get_a_record "$NODE")"
     ip6="$(get_aaaa_record "$NODE")"
     IP46ADDRs=$(newline_to_spaces "$ip4 $ip6")

     if [[ -n "$CMDLINE_IP" ]]; then
          # command line has supplied an IP address or "one"
          if [[ "$CMDLINE_IP" == one ]]; then
               # use first IPv6 or IPv4 address
               if "$HAS_IPv6" && [[ -n "$ip6" ]]; then
                    CMDLINE_IP="$(head -1 <<< "$ip6")"
               else
                    CMDLINE_IP="$(head -1 <<< "$ip4")"
               fi
          fi
          NODEIP="$CMDLINE_IP"
          if is_ipv4addr "$NODEIP"; then
               ip4="$NODEIP"
          elif is_ipv6addr "$NODEIP"; then
               ip6="$NODEIP"
          else
               fatal "couldn't identify supplied \"CMDLINE_IP\"" $ERR_DNSLOOKUP
          fi
     elif is_ipv4addr "$NODE"; then
          ip4="$NODE"                        # only an IPv4 address was supplied as an argument, no hostname
          SNI=""                             # override Server Name Indication as we test the IP only
     else
          ip4=$(get_local_a "$NODE")         # is there a local host entry?
          if [[ -z "$ip4" ]]; then           # empty: no (LOCAL_A is predefined as false)
               ip4=$(get_a_record "$NODE")
          else
               LOCAL_A=true                  # we have the ip4 from local host entry and need to signal this to testssl
          fi
          # same now for ipv6
          ip6=$(get_local_aaaa "$NODE")
          if [[ -z "$ip6" ]]; then
               ip6=$(get_aaaa_record "$NODE")
          else
               LOCAL_AAAA=true               # we have a local ipv6 entry and need to signal this to testssl
          fi
     fi

     # IPv6 only address
     if [[ -z "$ip4" ]]; then
          if "$HAS_IPv6"; then
               IPADDRs=$(newline_to_spaces "$ip6")
               IP46ADDRs="$IPADDRs"          # IP46ADDRs are the ones to display, IPADDRs the ones to test
          fi
     else
          if "$HAS_IPv6" && [[ -n "$ip6" ]]; then
               if is_ipv6addr "$CMDLINE_IP"; then
                    IPADDRs=$(newline_to_spaces "$ip6")
               else
                    IPADDRs=$(newline_to_spaces "$ip4 $ip6")
               fi
          else
               IPADDRs=$(newline_to_spaces "$ip4")
          fi
     fi
     if [[ -z "$IPADDRs" ]]; then
          if [[ -n "$ip6" ]]; then
               fatal "Only IPv6 address(es) for \"$NODE\" available, maybe add \"-6\" to $0" $ERR_DNSLOOKUP
          else
               fatal "No IPv4/IPv6 address(es) for \"$NODE\" available" $ERR_DNSLOOKUP
          fi
     fi
     return 0                                # IPADDR and IP46ADDR is set now
}

determine_rdns() {
     local saved_openssl_conf="$OPENSSL_CONF"
     local nodeip="" rdns="" line=""

     [[ -n "$NODNS" ]] && rDNS="(instructed to minimize DNS queries)" && return 0   # PTR records were not asked for
     local nodeip="$(tr -d '[]' <<< $NODEIP)"     # for DNS we do not need the square brackets of IPv6 addresses
     OPENSSL_CONF=""                              # see https://github.com/drwetter/testssl.sh/issues/134
     if [[ "$NODE" == *.local ]]; then
          if "$HAS_AVAHIRESOLVE"; then
               rDNS=$(avahi-resolve -a $nodeip 2>/dev/null | awk '{ print $2 }')
          elif "$HAS_DIG"; then
               rDNS=$(dig -x $nodeip @224.0.0.251 -p 5353 +notcp +noall +answer +short | awk '{ print $1 }')
          fi
     elif "$HAS_DIG"; then
          # 1+2 should suffice. It's a compromise for if e.g. network is down but we have a docker/localhost server
          rDNS=$(dig -x $nodeip +timeout=1 +tries=2 +noall +answer +short | awk '{ print $1 }')    # +short returns also CNAME, e.g. openssl.org
     elif "$HAS_HOST"; then
          rDNS=$(host -t PTR $nodeip 2>/dev/null | awk '/pointer/ { print $NF }')
     elif "$HAS_DRILL"; then
          rDNS=$(drill -x ptr $nodeip 2>/dev/null | awk '/ANSWER SECTION/ { getline; print $NF }')
     elif "$HAS_NSLOOKUP"; then
          rDNS=$(strip_lf "$(nslookup -type=PTR $nodeip 2>/dev/null | grep -v 'canonical name =' | grep 'name = ' | awk '{ print $NF }' | sed 's/\.$//')")
     fi
     OPENSSL_CONF="$saved_openssl_conf"      # see https://github.com/drwetter/testssl.sh/issues/134
     # First, rDNS can contain multilines due to multiple PTR DNS records, though this is not recommended.
     # So we use a loop to check for each FQDN returned. There we remove chars which under weird
     # circumstances (see #1506) can show up here. The blacklist is taken from RFC 1912 ("Allowable characters in a
     # label for a host name are only ASCII, letters, digits, and the `-' character")
     while read -r line; do
          line="$(tr -dc '[a-zA-Z0-9-_.]' <<< "$line")"
          [[ -z "$rdns" ]] && rdns="$line" || rdns="$rdns $line"
     done <<< "$rDNS"
     rDNS="$rdns"
     [[ -z "$rDNS" ]] && rDNS="--"
     return 0
}

# We need to get the IP address of the proxy so we can use it in fd_socket
#
check_proxy() {
     if [[ -n "$PROXY" ]]; then
          if ! "$HAS_PROXY"; then
               fatal "Your $OPENSSL is too old to support the \"-proxy\" option" $ERR_OSSLBIN
          fi
          if [[ "$PROXY" == auto ]]; then
               # Get $ENV https_proxy is the one we care about for connects
               PROXY="${https_proxy#*\/\/}"
               # Fallback:
               [[ -z "$PROXY" ]] && PROXY="${http_proxy#*\/\/}"
               [[ -z "$PROXY" ]] && fatal "you specified \"--proxy=auto\" but \"\$http(s)_proxy\" is empty" $ERR_CMDLINE
          fi
          # strip off http/https part if supplied:
          PROXY="${PROXY/http\:\/\//}"
          PROXY="${PROXY/https\:\/\//}"      # this shouldn't be needed
          PROXYNODE="${PROXY%:*}"
          PROXYPORT="${PROXY#*:}"
          is_number "$PROXYPORT" || fatal "Proxy port cannot be determined from \"$PROXY\"" $ERR_CMDLINE

          #if is_ipv4addr "$PROXYNODE" || is_ipv6addr "$PROXYNODE" ; then
          # IPv6 via openssl -proxy: that doesn't work. Sockets does
#FIXME: finish this with LibreSSL which supports an IPv6 proxy
          if is_ipv4addr "$PROXYNODE"; then
               PROXYIP="$PROXYNODE"
          else
               PROXYIP="$(get_a_record "$PROXYNODE" 2>/dev/null | grep -v alias | sed 's/^.*address //')"
               [[ -z "$PROXYIP" ]] && fatal "Proxy IP cannot be determined from \"$PROXYNODE\"" $ERR_CMDLINE
          fi
          PROXY="-proxy $PROXYIP:$PROXYPORT"
     fi
}


# this is only being called from determine_optimal_proto in order to check whether we have a server
# with client authentication, a server with no SSL session ID switched off
#
sclient_auth() {
     [[ $1 -eq 0 ]] && return 0                                            # no client auth (CLIENT_AUTH=false is preset globally)
     if [[ -n $(awk '/Master-Key: / { print $2 }' "$2") ]]; then           # connect succeeded
          if grep -q '^<<< .*CertificateRequest' "$2"; then                # CertificateRequest message in -msg
               CLIENT_AUTH=true
               return 0
          fi
          if [[ -z $(awk '/Session-ID: / { print $2 }' "$2") ]]; then      # probably no SSL session
               if [[ 2 -eq $(grep -c CERTIFICATE "$2") ]]; then            # do another sanity check to be sure
                    CLIENT_AUTH=false
                    NO_SSL_SESSIONID=true                                  # NO_SSL_SESSIONID is preset globally to false for all other cases
                    return 0
               fi
          fi
     fi
     # what's left now is: master key empty, handshake returned not successful, session ID empty --> not successful
     return 1
}

# Determine the best parameters to use with tls_sockets():
#   For TLSv1.3, determine what extension number to use for the key_share extension.
#   For TLSv1.2, determine what cipher list to send, since there are more than 128
#   TLSv1.2 ciphers and some servers fail if the ClientHello contains too many ciphers.
# If both TLSv1.3 and TLSv1.2 ClientHello messages result in failed connection attempts,
# then try to determine whether:
#   (1) This is an SSLv2-only server
#   (2) This server supports some protocol in SSLv3 - TLSv1.1, but cannot handle version negotiation.
#   (3) This is not a TLS/SSL enabled server.
# This information can be used by determine_optimal_proto() to help distinguish between a server
# that is not TLS/SSL enabled and one that is not compatible with the version of OpenSSL being used.
determine_optimal_sockets_params() {
     local -i ret1=1 ret2=1
     local i proto cipher_offered
     local all_failed=true

     # If a STARTTLS protocol is specified and $SSL_NATIVE is true, then skip this test, since
     # $SSL_NATIVE may have been set to true as a result of tls_sockets() not supporting the STARTTLS
     # protocol.
     [[ -n "$STARTTLS_PROTOCOL" ]] && "$SSL_NATIVE" && return 0

     # NOTE: The following code is only needed as long as draft versions of TLSv1.3 prior to draft 23
     # are supported. It is used to determine whether a draft 23 or pre-draft 23 ClientHello should be
     # sent.
     KEY_SHARE_EXTN_NR="33"
     tls_sockets "04" "$TLS13_CIPHER" "" "00, 2b, 00, 0f, 0e, 03,04, 7f,1c, 7f,1b, 7f,1a, 7f,19, 7f,18, 7f,17"
     if [[ $? -eq 0 ]]; then
          add_proto_offered tls1_3 yes
          all_failed=false
     else
          KEY_SHARE_EXTN_NR="28"
          tls_sockets "04" "$TLS13_CIPHER" "" "00, 2b, 00, 0b, 0a, 7f,16, 7f,15, 7f,14, 7f,13, 7f,12"
          if [[ $? -eq 0 ]]; then
               add_proto_offered tls1_3 yes
               all_failed=false
          else
               add_proto_offered tls1_3 no
               KEY_SHARE_EXTN_NR="33"
          fi
     fi
     if ! "$all_failed"; then
          # Determine which version of TLS 1.3 was offered. For drafts 18-21 the
          # version appears in the ProtocolVersion field of the ServerHello. For
          # drafts 22-28 and the final TLS 1.3 the ProtocolVersion field contains
          # 0303 and the actual version appears in the supported_versions extension.
          if [[ "${TLS_SERVER_HELLO:8:3}" == 7F1 ]]; then
               add_proto_offered tls1_3_draft$(hex2dec "${TLS_SERVER_HELLO:10:2}") yes
          elif [[ "$TLS_SERVER_HELLO" =~ 002B00020304 ]]; then
               add_proto_offered tls1_3_rfc8446 yes
          elif [[ "$TLS_SERVER_HELLO" =~ 002B00027F1[2-9A-C] ]]; then
               add_proto_offered tls1_3_draft$(hex2dec "${BASH_REMATCH:10:2}") yes
          fi
     fi

     # Need to determine which set of ciphers is best to use with
     # a TLSv1.2 ClientHello since there are far more than 128 ciphers
     # that can be used.
     tls_sockets "03" "$TLS12_CIPHER"
     ret1=$?
     if [[ $ret1 -eq 0 ]] || [[ $ret1 -eq 2 ]]; then
          case $DETECTED_TLS_VERSION in
               0303)  add_proto_offered tls1_2 yes ;;
               0302)  add_proto_offered tls1_1 yes ;;
               0301)  add_proto_offered tls1 yes ;;
               0300)  add_proto_offered ssl3 yes ;;
          esac
          all_failed=false
     fi

     # Try again with a different, less common, set of cipher suites
     # see #807 and #806. If using these cipher suites results in a
     # successful connection, then change $TLS12_CIPHER to these
     # cipher suites so that later tests will use this list of cipher
     # suites.
     if [[ $ret1 -ne 0 ]]; then
          tls_sockets "03" "$TLS12_CIPHER_2ND_TRY"
          ret2=$?
          if [[ $ret2 -eq 0 ]]; then
               add_proto_offered tls1_2 yes
               TLS12_CIPHER="$TLS12_CIPHER_2ND_TRY"
               all_failed=false
          else
               add_proto_offered tls1_2 no
          fi
          if [[ $ret2 -eq 2 ]]; then
               case $DETECTED_TLS_VERSION in
                    0302)  add_proto_offered tls1_1 yes ;;
                    0301)  add_proto_offered tls1 yes ;;
                    0300)  add_proto_offered ssl3 yes ;;
               esac
               [[ $ret1 -ne 2 ]] && TLS12_CIPHER="$TLS12_CIPHER_2ND_TRY"
               all_failed=false
          fi
     fi
     if [[ $ret1 -eq 0 ]] || [[ $ret2 -eq 0 ]]; then
          cipher_offered="$(get_cipher "$TEMPDIR/$NODEIP.parse_tls_serverhello.txt")"
          if [[ "$cipher_offered" == TLS_* ]] || [[ "$cipher_offered" == SSL_* ]]; then
               cipher_offered="$(rfc2hexcode "$cipher_offered")"
          else
               cipher_offered="$(openssl2hexcode "$cipher_offered")"
          fi
          [[ ${#cipher_offered} -eq 9 ]] && TLS12_CIPHER_OFFERED="${cipher_offered:2:2},${cipher_offered:7:2}"
     fi

     if "$all_failed"; then
          # One of the following must be true:
          #   * This is not a TLS/SSL enabled server.
          #   * The server only supports SSLv2
          #   * The server does not handle version negotiation correctly.
          for proto in 01 00 02; do
               tls_sockets "$proto" "$TLS_CIPHER" "" "" "true"
               ret1=$?
               if [[ $ret1 -ne 0 ]]; then
                    case $proto in
                         02)  add_proto_offered tls1_1 no ;;
                         01)  add_proto_offered tls1 no ;;
                         00)  add_proto_offered ssl3 no ;;
                    esac
               fi
               if [[ $ret1 -eq 0 ]] || [[ $ret1 -eq 2 ]]; then
                    case $DETECTED_TLS_VERSION in
                         0302)  add_proto_offered tls1_1 yes ;;
                         0301)  add_proto_offered tls1 yes ;;
                         0300)  add_proto_offered ssl3 yes ;;
                    esac
                    OPTIMAL_SOCKETS_PROTO="$proto"
                    all_failed=false
                    break
               fi
          done
     fi
     if "$all_failed"; then
          sslv2_sockets
          [[ $? -eq 3 ]] && all_failed=false && add_proto_offered ssl2 yes
     fi
     ALL_FAILED_SOCKETS="$all_failed"
     return 0
}


# This function determines (STARTTLS_)OPTIMAL_PROTO. It is basically a workaround function as under certain
# circumstances a ClientHello without specifying a protocol will fail.
# Circumstances observed so far: 1.) IIS 6 and openssl 1.0.2 as opposed to 1.0.1  2.) starttls + dovecot imap.
# Independent on the server side it seems reasonable to to know upfront which protocol always works
#
# arg1: if empty: no STARTTLS, else: STARTTLS protocol
# The first try in the loop is empty as we prefer not to specify always a protocol if we can get along w/o it
#
determine_optimal_proto() {
     local all_failed=true
     local tmp=""
     local proto optimal_proto

     "$do_tls_sockets" && return 0

     >$ERRFILE
     if [[ -n "$1" ]]; then
          # STARTTLS workaround needed see https://github.com/drwetter/testssl.sh/issues/188 -- kind of odd
          for STARTTLS_OPTIMAL_PROTO in -tls1_2 -tls1 -ssl3 -tls1_1 -tls1_3 -ssl2; do
               case $STARTTLS_OPTIMAL_PROTO in
                    -tls1_3) "$HAS_TLS13" || continue ;;
                    -ssl3)   "$HAS_SSL3" || continue ;;
                    -ssl2)   "$HAS_SSL2" || continue ;;
                    *) ;;
               esac
               $OPENSSL s_client $(s_client_options "$STARTTLS_OPTIMAL_PROTO $BUGS -connect "$NODEIP:$PORT" $PROXY -msg $STARTTLS $SNI") </dev/null >$TMPFILE 2>>$ERRFILE
               if sclient_auth $? $TMPFILE; then
                    all_failed=false
                    add_proto_offered "${STARTTLS_OPTIMAL_PROTO/-/}" yes
                    break
               fi
          done
          "$all_failed" && STARTTLS_OPTIMAL_PROTO=""
          optimal_proto="$STARTTLS_OPTIMAL_PROTO"
          debugme echo "STARTTLS_OPTIMAL_PROTO: $STARTTLS_OPTIMAL_PROTO"
     else
          # No STARTTLS
          for proto in '' -tls1_2 -tls1 -tls1_3 -ssl3 -tls1_1 -ssl2; do
               case $proto in
                    -tls1_3) "$HAS_TLS13" || continue ;;
                    -ssl3)   "$HAS_SSL3" || continue ;;
                    -ssl2)   "$HAS_SSL2" || continue ;;
                    *) ;;
               esac
               $OPENSSL s_client $(s_client_options "$proto $BUGS -connect "$NODEIP:$PORT" -msg $PROXY $SNI") </dev/null >$TMPFILE 2>>$ERRFILE
               if sclient_auth $? $TMPFILE; then
                    # we use the successful handshake at least to get one valid protocol supported -- it saves us time later
                    if [[ -z "$proto" ]]; then
                         # convert to openssl terminology
                         tmp=$(get_protocol $TMPFILE)
                         tmp=${tmp/\./_}
                         tmp=${tmp/v/}
                         tmp="$(tolower $tmp)"
                         add_proto_offered "${tmp}" yes
                         debugme echo "one proto determined: $tmp"
                         OPTIMAL_PROTO=""
                    else
                         add_proto_offered "${proto/-/}" yes
                         OPTIMAL_PROTO="$proto"
                    fi
                    all_failed=false
                    break
               fi
          done
          "$all_failed" && OPTIMAL_PROTO=""
          optimal_proto="$OPTIMAL_PROTO"

          debugme echo "OPTIMAL_PROTO: $OPTIMAL_PROTO"
     fi
     [[ "$optimal_proto" != -ssl2 ]]  && ! "$all_failed" && grep -q '^Server Temp Key' $TMPFILE && HAS_DH_BITS=true     # FIX #190
     if [[ "$(has_server_protocol "tls1_3")" -eq 0 ]] && [[ "$(has_server_protocol "tls1_2")" -ne 0 ]] &&
        [[ "$(has_server_protocol "tls1_1")" -ne 0 ]] && [[ "$(has_server_protocol "tls1")" -ne 0 ]] &&
        [[ "$(has_server_protocol "ssl3")" -ne 0 ]]; then
          TLS13_ONLY=true
     fi

     if [[ "$optimal_proto" == -ssl2 ]]; then
          prln_magenta "$NODEIP:$PORT appears to only support SSLv2."
          ignore_no_or_lame " Type \"yes\" to proceed and accept false negatives or positives" "yes"
          [[ $? -ne 0 ]] && exit $ERR_CLUELESS
     elif "$all_failed" && ! "$ALL_FAILED_SOCKETS"; then
          if ! "$HAS_TLS13" && "$TLS13_ONLY"; then
               pr_magenta " $NODE:$PORT appears to support TLS 1.3 ONLY. You better use --openssl=<path_to_openssl_supporting_TLS_1.3>"
               if ! "$OSSL_SHORTCUT" || [[ ! -x /usr/bin/openssl ]] || /usr/bin/openssl s_client -tls1_3 -connect invalid. 2>&1 | grep -aiq "unknown option"; then
                    outln
                    ignore_no_or_lame " Type \"yes\" to proceed and accept all scan problems" "yes"
                    [[ $? -ne 0 ]] && exit $ERR_CLUELESS
                    MAX_OSSL_FAIL=10
               else
                    # dirty hack but an idea for the future to be implemented upfront: Now we know, we'll better off
                    # with the OS supplied openssl binary. We need to inittialize variables / arrays again though.
                    # And the service detection can't be made up for now
                    outln ", \n proceeding with /usr/bin/openssl"
                    OPENSSL=/usr/bin/openssl
                    find_openssl_binary
                    prepare_arrays
               fi
          elif ! "$HAS_SSL3" && [[ "$(has_server_protocol "ssl3")" -eq 0 ]] && [[ "$(has_server_protocol "tls1_3")" -ne 0 ]] && \
               [[ "$(has_server_protocol "tls1_2")" -ne 0 ]] && [[ "$(has_server_protocol "tls1_1")" -ne 0 ]] &&
               [[ "$(has_server_protocol "tls1")" -ne 0 ]]; then
               prln_magenta " $NODE:$PORT appears to support SSLv3 ONLY. You better use --openssl=<path_to_openssl_supporting_SSL_3>"
               ignore_no_or_lame " Type \"yes\" to proceed and accept all scan problems" "yes"
               [[ $? -ne 0 ]] && exit $ERR_CLUELESS
               MAX_OSSL_FAIL=10
          else
               prln_bold " Your OpenSSL cannot connect to $NODEIP:$PORT"
               ignore_no_or_lame " The results might look ok but they could be nonsense. Really proceed ? (\"yes\" to continue)" "yes"
               [[ $? -ne 0 ]] && exit $ERR_CLUELESS
          fi
     elif "$all_failed"; then
          outln
          if "$HAS_IPv6"; then
               pr_bold " Your $OPENSSL is not IPv6 aware, or $NODEIP:$PORT "
          else
               pr_bold " $NODEIP:$PORT "
          fi
          tmpfile_handle ${FUNCNAME[0]}.txt
          prln_bold "doesn't seem to be a TLS/SSL enabled server";
          ignore_no_or_lame " The results might look ok but they could be nonsense. Really proceed ? (\"yes\" to continue)" "yes"
          [[ $? -ne 0 ]] && exit $ERR_CLUELESS
     elif ! "$all_failed" && "$ALL_FAILED_SOCKETS" && ! "$SSL_NATIVE"; then
          # For some reason connecting with tls_sockets/sslv2_sockets didn't work, but connecting
          # with $OPENSSL s_client did.
          # FIXME: Should we include some sort of "please report" note here?
          prln_magenta " Testing with $NODE:$PORT only worked using $OPENSSL."
          prln_magenta " Test results may be somewhat better if the --ssl-native option is used."
          ignore_no_or_lame " Type \"yes\" to proceed and accept false negatives or positives" "yes"
          [[ $? -ne 0 ]] && exit $ERR_CLUELESS
     fi

     tmpfile_handle ${FUNCNAME[0]}.txt
     return 0
}


# arg1 (optional): ftp smtp, lmtp, pop3, imap, xmpp, xmpp-server, telnet, ldap, postgres, mysql, irc, nntp (maybe with trailing s)
#
determine_service() {
     local ua
     local protocol
     local basicauth_header=""

     # Check if we can connect to $NODEIP:$PORT. Attention: This ALWAYS uses sockets. Thus timeouts for --ssl-=native do not apply
     if ! fd_socket 5; then
          if [[ -n "$PROXY" ]]; then
               fatal "You're sure $PROXYNODE:$PROXYPORT allows tunneling here? Can't connect to \"$NODEIP:$PORT\"" $ERR_CONNECT
          else
               if "$MULTIPLE_CHECKS"; then
                    ip_fatal "Couldn't connect to $NODEIP:$PORT"
                    return 1
               else
                    fatal "Can't connect to \"$NODEIP:$PORT\"\nMake sure a firewall is not between you and your scanning target!" $ERR_CONNECT
               fi
          fi
     fi
     close_socket

     outln
     if [[ -z "$1" ]]; then
          # no STARTTLS.
          determine_optimal_sockets_params
          determine_optimal_proto
          $SNEAKY && \
               ua="$UA_SNEAKY" || \
               ua="$UA_STD"
          if [[ -n "$BASICAUTH" ]]; then
               basicauth_header="Authorization: Basic $(safe_echo "$BASICAUTH" | $OPENSSL base64 2>/dev/null)\r\n"
          fi
          GET_REQ11="GET $URL_PATH HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $ua\r\n${basicauth_header}Accept-Encoding: identity\r\nAccept: text/*\r\nConnection: Close\r\n\r\n"
          # returns always 0:
          service_detection $OPTIMAL_PROTO
     else # STARTTLS
          if [[ "$1" == postgres ]]; then
               protocol="postgres"
          else
               protocol=${1%s}     # strip trailing 's' in ftp(s), smtp(s), pop3(s), etc
          fi

          case "$protocol" in
               ftp|smtp|lmtp|pop3|imap|xmpp|xmpp-server|telnet|ldap|postgres|mysql|nntp)
                    STARTTLS="-starttls $protocol"
                    if [[ "$protocol" == xmpp ]] || [[ "$protocol" == xmpp-server ]]; then
                         # for XMPP, openssl has a problem using -connect $NODEIP:$PORT. thus we use -connect $NODE:$PORT instead!
                         NODEIP="$NODE"
                         if [[ -n "$XMPP_HOST" ]]; then
                              if ! "$HAS_XMPP"; then
                                   fatal "Your $OPENSSL does not support the \"-xmpphost\" option" $ERR_OSSLBIN
                              fi
                              STARTTLS="$STARTTLS -xmpphost $XMPP_HOST"         # small hack -- instead of changing calls all over the place
                              # see https://xmpp.org/rfcs/rfc3920.html
                         else
                              if is_ipv4addr "$NODE"; then
                                   # XMPP needs a jabber domainname
                                   if [[ -n "$rDNS" ]]; then
                                        prln_warning " IP address doesn't work for XMPP, trying PTR record $rDNS"
                                        # remove trailing .
                                        NODE=${rDNS%%.}
                                        NODEIP=${rDNS%%.}
                                   else
                                        fatal "No DNS supplied and no PTR record available which I can try for XMPP" $ERR_DNSLOOKUP
                                   fi
                              fi
                         fi
                         if [[ "$protocol" == xmpp-server ]] && ! "$HAS_XMPP_SERVER"; then
                              fatal "Your $OPENSSL does not support the \"-starttls xmpp-server\" option" $ERR_OSSLBIN
                         fi
                    elif [[ "$protocol" == postgres ]]; then
                         # Check if openssl version supports postgres.
                         if ! "$HAS_POSTGRES"; then
                              fatal "Your $OPENSSL does not support the \"-starttls postgres\" option" $ERR_OSSLBIN
                         fi
                    elif [[ "$protocol" == mysql ]]; then
                         # Check if openssl version supports mysql.
                         if ! "$HAS_MYSQL"; then
                              fatal "Your $OPENSSL does not support the \"-starttls mysql\" option" $ERR_OSSLBIN
                         fi
                    elif [[ "$protocol" == lmtp ]]; then
                         # Check if openssl version supports lmtp.
                         if ! "$HAS_LMTP"; then
                              fatal "Your $OPENSSL does not support the \"-starttls lmtp\" option" $ERR_OSSLBIN
                         fi
                    elif [[ "$protocol" == nntp ]]; then
                         # Check if openssl version supports lmtp.
                         if ! "$HAS_NNTP"; then
                              fatal "Your $OPENSSL does not support the \"-starttls nntp\" option" $ERR_OSSLBIN
                         fi
                    fi
                    determine_optimal_sockets_params
                    determine_optimal_proto "$1"

                    out " Service set:$CORRECT_SPACES            STARTTLS via "
                    out "$(toupper "$protocol")"
                    [[ "$protocol" == mysql ]] && out " (experimental)"
                    fileout "service" "INFO" "$protocol"
                    [[ -n "$XMPP_HOST" ]] && out " (XMPP domain=\'$XMPP_HOST\')"
                    outln
                    ;;
               *)   outln
                    fatal "momentarily only ftp, smtp, lmtp, pop3, imap, xmpp, xmpp-server, telnet, ldap, nntp, postgres and mysql allowed" $ERR_CMDLINE
                    ;;
          esac
     fi
     tmpfile_handle ${FUNCNAME[0]}.txt
     return 0       # OPTIMAL_PROTO, GET_REQ*/HEAD_REQ* is set now
}


# Sets SERVER_SIZE_LIMIT_BUG to true or false, depending on whether we hit the 128 cipher limit.
# Return value is 0 unless we have a problem executing
#
determine_sizelimitbug() {
     # overflow_cipher must be some cipher that does not appear in TLS12_CIPHER.
     local overflow_cipher='C0,86'
     local -i nr_ciphers

     # For STARTTLS protcols not being implemented yet via sockets this is a bypass otherwise it won't be usable at all (e.g. LDAP)
     # Fixme: find out whether we can't skip this in general for STARTTLS
     [[ "$STARTTLS" =~ ldap ]] && return 0
     [[ "$STARTTLS" =~ irc ]] && return 0

     # Only with TLS 1.2 offered at the server side it is possible to hit this bug, in practise. Thus
     # we assume if TLS 1.2 is not supported, the server has no cipher size limit bug. It still may,
     # theoretically, but in a regular check with testssl.sh we won't hit this limit with lower protocols.
     # Upon calling this function we already know whether TLS 1.2 is supported. If TLS 1.2 is supported, we
     # send 129 ciphers (including 00FF) and check whether it works.

     if [[ 1 -eq $(has_server_protocol 03) ]]; then
          SERVER_SIZE_LIMIT_BUG=false
     else
          if [[ "$DEBUG" -ge 1 ]]; then
               nr_ciphers="$(tr ' ' '\n' <<< "${overflow_cipher}, $TLS12_CIPHER" | sed -e '/^$/d' | wc -l)"
               if [[ $nr_ciphers -ne 129 ]]; then
                    prln_warning "FIXME line $LINENO, ${FUNCNAME[0]} sending $nr_ciphers ciphers rather than 129."
               else
                    debugme echo "${FUNCNAME[0]} sending $nr_ciphers ciphers"
               fi
          fi
          tls_sockets 03 "${overflow_cipher}, ${TLS12_CIPHER}"
          if [[ $? -eq 0 ]]; then
               SERVER_SIZE_LIMIT_BUG=false
          else
               SERVER_SIZE_LIMIT_BUG=true
          fi
          debugme echo -e "\nSERVER_SIZE_LIMIT_BUG: $SERVER_SIZE_LIMIT_BUG"
     fi
     if "$SERVER_SIZE_LIMIT_BUG"; then
          out " Pre-test: "
          prln_svrty_medium "128 cipher limit bug"
          fileout "pre_128cipher" "MEDIUM" "128 cipher limit bug"
     else
          [[ "$DEBUG" -ge 1 ]] && outln " Pre-test: No 128 cipher limit bug"
          fileout "pre_128cipher" "INFO" "No 128 cipher limit bug"
     fi
     return 0
}


display_rdns_etc() {
     local ip further_ip_addrs=""
     local nodeip="$(tr -d '[]' <<< $NODEIP)"     # for displaying IPv6 addresses we don't need []

     if [[ -n "$PROXY" ]]; then
          out " Via Proxy:              $CORRECT_SPACES"
          outln "$PROXYIP:$PROXYPORT "
     fi
     if [[ $(count_words "$IP46ADDRs") -gt 1 ]]; then
          out " Further IP addresses:   $CORRECT_SPACES"
          for ip in $IP46ADDRs; do
               if [[ "$ip" == "$NODEIP" ]] || [[ "[$ip]" == "$NODEIP" ]]; then
                    continue
               else
                    further_ip_addrs+="$ip "
               fi
          done
          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 "
     elif "$LOCAL_AAAA"; then
          outln " AAAA record via:       $CORRECT_SPACES /etc/hosts "
     elif  [[ -n "$CMDLINE_IP" ]]; then
          if is_ipv6addr $"$CMDLINE_IP"; then
               outln " AAAA record via:       $CORRECT_SPACES supplied IP \"$CMDLINE_IP\""
          else
               outln " A record via:          $CORRECT_SPACES supplied IP \"$CMDLINE_IP\""
          fi
     fi
     if [[ "$rDNS" =~ instructed ]]; then
          out "$(printf " %-23s " "rDNS ($nodeip):")"
          out "$rDNS"
     elif [[ -n "$rDNS" ]]; then
          out "$(printf " %-23s " "rDNS ($nodeip):")"
          out "$(out_row_aligned_max_width "$rDNS" "                         $CORRECT_SPACES" $TERM_WIDTH)"
     fi
}

datebanner() {
     local scan_time_f=""

     if [[ "$1" =~ Done ]] ; then
          scan_time_f="$(printf "%04ss" "$SCAN_TIME")"           # 4 digits because of windows
          pr_reverse "$1 $(date +%F) $(date +%T) [$scan_time_f] -->> $NODEIP:$PORT ($NODE) <<--"
     else
          pr_reverse "$1 $(date +%F) $(date +%T)        -->> $NODEIP:$PORT ($NODE) <<--"
     fi
     outln "\n"
     [[ "$1" =~ Start ]] && display_rdns_etc
}

# one line with char $1 over screen width $2
draw_line() {
     out "$(printf -- "$1"'%.s' $(eval "echo {1.."$(($2))"}"))"
}


run_mx_all_ips() {
     local mxs mx
     local mxport
     local -i ret=0
     local word=""

     STARTTLS_PROTOCOL="smtp"
     # test first higher priority servers
     mxs=$(get_mx_record "$1" | sort -n | sed -e 's/^.* //' -e 's/\.$//' | tr '\n' ' ')
     if [[ $CMDLINE_IP == one ]]; then
          word="as instructed one"                               # with highest priority
          mxs=${mxs%% *}
     else
          word="the only"
     fi
     mxport=${2:-25}
     if [[ -n "$LOGFILE" ]]; then
          prepare_logging
     else
          prepare_logging "${FNAME_PREFIX}mx-$1"
     fi
     if [[ -n "$mxs" ]] && [[ "$mxs" != ' ' ]]; then
          [[ $(count_words "$mxs") -gt 1 ]] && MULTIPLE_CHECKS=true
          if "$MULTIPLE_CHECKS"; then
               pr_bold "Testing all MX records (on port $mxport): "
          else
               pr_bold "Testing $word MX record (on port $mxport): "
          fi
          outln "$mxs"
          [[ $mxport == 465 ]] &&  STARTTLS_PROTOCOL=""          # no starttls for tcp 465, all other ports are starttls
          for mx in $mxs; do
               draw_line "-" $((TERM_WIDTH * 2 / 3))
               outln
               parse_hn_port "$mx:$mxport"
               determine_ip_addresses || continue
               if [[ $(count_words "$IPADDRs") -gt 1 ]]; then    # we have more than one ipv4 address to check
                    MULTIPLE_CHECKS=true
                    pr_bold "Testing all IPv4 addresses (port $PORT): "; outln "$IPADDRs"
                    for ip in $IPADDRs; do
                         NODEIP="$ip"
                         lets_roll "${STARTTLS_PROTOCOL}"
                    done
               else
                    NODEIP="$IPADDRs"
                    lets_roll "${STARTTLS_PROTOCOL}"
               fi
               ret=$(($? + ret))
          done
          draw_line "-" $((TERM_WIDTH * 2 / 3))
          outln
          pr_bold "Done testing all MX records (on port $mxport): "; outln "$mxs"
     else
          prln_bold " $1 has no MX records(s)"
     fi
     return $ret
}

# If run_mass_testing() is being used, then create the command line
# for the test based on the global command line (all elements of the
# command line provided to the parent, except the --file/-iL option) and the
# specific command line options for the test to be run. Each argument
# in the command line needs to be a separate element in an array in order
# to deal with word splitting within file names (see #702).
#
# If run_mass_testing_parallel() is being used, then in addition to the above,
# modify global command line for child tests so that if all (JSON, CSV, HTML)
# output is to go into a single file, each child will have its output placed in
# a separate, named file, so that the separate files can be concatenated
# together once they are complete to create the single file.
#
# If run_mass_testing() is being used, then "$1" is "serial". If
# run_mass_testing_parallel() is being used, then "$1" is "parallel XXXXXXXX"
# where XXXXXXXX is the number of the test being run.
#
create_mass_testing_cmdline() {
     local testing_type="$1"
     local cmd test_number
     local outfile_arg
     local -i nr_cmds=0 i=0
     local skip_next=false

     MASS_TESTING_CMDLINE=()
     [[ "$testing_type" =~ parallel ]] && read -r testing_type test_number <<< "$testing_type"

     # Start by adding the elements from the global command line to the command line for the
     # test. If run_mass_testing_parallel(), then modify the command line so that, when
     # required, each child process sends its test results to a separate file.  If a cmd
     # uses '=' for supplying a value we just skip next parameter (we don't use 'parse_opt_equal_sign' here)
     debugme echo "${CMDLINE_ARRAY[@]}"
     for cmd in "${CMDLINE_ARRAY[@]}"; do
          "$skip_next" && skip_next=false && i+=1 && continue
          if [[ "$cmd" =~ --file ]] || [[ "$cmd" =~ -iL ]]; then
               # Don't include the "--file[=...] or -iL argument in the child's command
               # line, but do include "--warnings=batch".
               MASS_TESTING_CMDLINE[nr_cmds]="--warnings=batch"
               nr_cmds+=1
               # next is the file itself, as no '=' was supplied
               [[ "$cmd" == --file ]] && skip_next=true
               [[ "$cmd" == -iL ]] && skip_next=true
          elif [[ "$testing_type" == serial ]]; then
               if "$JSONHEADER" && ( [[ "$cmd" =~ --jsonfile-pretty ]] || [[ "$cmd" =~ -oJ ]] ); then
                    >"$TEMPDIR/jsonfile_child.json"
                    MASS_TESTING_CMDLINE[nr_cmds]="--jsonfile-pretty=$TEMPDIR/jsonfile_child.json"
                    # next is the jsonfile itself, as no '=' was supplied
                    [[ "$cmd" == --jsonfile-pretty ]] && skip_next=true
                    [[ "$cmd" == -oJ ]] && skip_next=true
               elif "$JSONHEADER" && ( [[ "$cmd" =~ --jsonfile ]] || [[ "$cmd" =~ -oj ]] ); then
                    >"$TEMPDIR/jsonfile_child.json"
                    MASS_TESTING_CMDLINE[nr_cmds]="--jsonfile=$TEMPDIR/jsonfile_child.json"
                    # next is the jsonfile itself, as no '=' was supplied
                    [[ "$cmd" == --jsonfile ]] && skip_next=true
                    [[ "$cmd" == -oj ]] && skip_next=true
               elif "$JSONHEADER" && ( [[ "$cmd" =~ --outFile ]] || [[ "$cmd" =~ -oA ]] ); then
                    outfile_arg="$(parse_opt_equal_sign "$cmd" "${CMDLINE_ARRAY[i+1]}")"
                    >"$TEMPDIR/jsonfile_child.json"
                    MASS_TESTING_CMDLINE[nr_cmds]="-oJ=$TEMPDIR/jsonfile_child.json"
                    nr_cmds+=1
                    MASS_TESTING_CMDLINE[nr_cmds]="-oC=$outfile_arg.csv"
                    nr_cmds+=1
                    MASS_TESTING_CMDLINE[nr_cmds]="-oH=$outfile_arg.html"
                    # next is the filename itself, as no '=' was supplied
                    [[ "$cmd" == --outFile ]] && skip_next=true
                    [[ "$cmd" == -oA ]] && skip_next=true
               elif "$JSONHEADER" && ( [[ "$cmd" =~ --outfile ]] || [[ "$cmd" =~ -oa ]] ); then
                    outfile_arg="$(parse_opt_equal_sign "$cmd" "${CMDLINE_ARRAY[i+1]}")"
                    >"$TEMPDIR/jsonfile_child.json"
                    MASS_TESTING_CMDLINE[nr_cmds]="-oj=$TEMPDIR/jsonfile_child.json"
                    nr_cmds+=1
                    MASS_TESTING_CMDLINE[nr_cmds]="-oC=$outfile_arg.csv"
                    nr_cmds+=1
                    MASS_TESTING_CMDLINE[nr_cmds]="-oH=$outfile_arg.html"
                    # next is the filename itself, as no '=' was supplied
                    [[ "$cmd" == --outfile ]] && skip_next=true
                    [[ "$cmd" == -oa ]] && skip_next=true
               else
                    MASS_TESTING_CMDLINE[nr_cmds]="$cmd"
               fi
               nr_cmds+=1
          else
               case "$cmd" in
                    --jsonfile|--jsonfile=*|-oj|-oj=*)
                         # If <jsonfile> is a file, then have provide a different
                         # file name to each child process. If <jsonfile> is a
                         # directory, then just pass it on to the child processes.
                         if "$JSONHEADER"; then
                              MASS_TESTING_CMDLINE[nr_cmds]="--jsonfile=$TEMPDIR/jsonfile_${test_number}.json"
                              # next is the jsonfile itself, as no '=' was supplied
                              [[ "$cmd" == --jsonfile ]] && skip_next=true
                              [[ "$cmd" == -oj ]] && skip_next=true
                         else
                              MASS_TESTING_CMDLINE[nr_cmds]="$cmd"
                         fi
                         ;;
                    --jsonfile-pretty|--jsonfile-pretty=*|-oJ|-oJ=*)
                         if "$JSONHEADER"; then
                              MASS_TESTING_CMDLINE[nr_cmds]="--jsonfile-pretty=$TEMPDIR/jsonfile_${test_number}.json"
                              [[ "$cmd" == --jsonfile-pretty ]] && skip_next=true
                              [[ "$cmd" == -oJ ]] && skip_next=true
                         else
                              MASS_TESTING_CMDLINE[nr_cmds]="$cmd"
                         fi
                         ;;
                    --csvfile|--csvfile=*|-oC|-oC=*)
                         if "$CSVHEADER"; then
                              MASS_TESTING_CMDLINE[nr_cmds]="--csvfile=$TEMPDIR/csvfile_${test_number}.csv"
                              [[ "$cmd" == --csvfile ]] && skip_next=true
                              [[ "$cmd" == -oC ]] && skip_next=true
                         else
                              MASS_TESTING_CMDLINE[nr_cmds]="$cmd"
                         fi
                         ;;
                    --htmlfile|--htmlfile=*|-oH|-oH=*)
                         if "$HTMLHEADER"; then
                              MASS_TESTING_CMDLINE[nr_cmds]="--htmlfile=$TEMPDIR/htmlfile_${test_number}.html"
                              [[ "$cmd" == --htmlfile ]] && skip_next=true
                              [[ "$cmd" == -oH ]] && skip_next=true
                         else
                              MASS_TESTING_CMDLINE[nr_cmds]="$cmd"
                         fi
                         ;;
                    --outfile|--outfile=*|-oa|-oa=*)
                         if "$JSONHEADER"; then
                              MASS_TESTING_CMDLINE[nr_cmds]="-oj=$TEMPDIR/jsonfile_${test_number}.json"
                              nr_cmds+=1
                              MASS_TESTING_CMDLINE[nr_cmds]="-oC=$TEMPDIR/csvfile_${test_number}.csv"
                              nr_cmds+=1
                              MASS_TESTING_CMDLINE[nr_cmds]="-oH=$TEMPDIR/htmlfile_${test_number}.html"
                              # next is the filename itself, as no '=' was supplied
                              [[ "$cmd" == --outfile ]] && skip_next=true
                              [[ "$cmd" == -oa ]] && skip_next=true
                         else
                              MASS_TESTING_CMDLINE[nr_cmds]="$cmd"
                         fi
                         ;;
                    --outFile|--outFile=*|-oA|-oA=*)
                         if "$JSONHEADER"; then
                              MASS_TESTING_CMDLINE[nr_cmds]="-oJ=$TEMPDIR/jsonfile_${test_number}.json"
                              nr_cmds+=1
                              MASS_TESTING_CMDLINE[nr_cmds]="-oC=$TEMPDIR/csvfile_${test_number}.csv"
                              nr_cmds+=1
                              MASS_TESTING_CMDLINE[nr_cmds]="-oH=$TEMPDIR/htmlfile_${test_number}.html"
                              # next is the filename itself, as no '=' was supplied
                              [[ "$cmd" == --outFile ]] && skip_next=true
                              [[ "$cmd" == -oA ]] && skip_next=true
                         else
                              MASS_TESTING_CMDLINE[nr_cmds]="$cmd"
                         fi
                         ;;
                    *)
                         MASS_TESTING_CMDLINE[nr_cmds]="$cmd"
                         ;;
               esac
               nr_cmds+=1
          fi
          i+=1
     done

     # Now add the command line arguments for the specific test to the command line.
     # Skip the first argument sent to this function, since it specifies the type of testing being performed.
     shift
     while [[ $# -gt 0 ]]; do
          MASS_TESTING_CMDLINE[nr_cmds]="$1"
          nr_cmds+=1
          shift
     done

     return 0
}


ports2starttls() {
     local tcp_port=$1
     local ret=0

     # https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers
     case $tcp_port in
          21)       echo "-t ftp " ;;
          23)       echo "-t telnet " ;;
          119|433)  echo "-t nntp " ;;   # to come
          25|587)   echo "-t smtp " ;;
          110)      echo "-t pop3 " ;;
          143)      echo "-t imap " ;;
          389)      echo "-t ldap ";;
          3306)     echo "-t mysql " ;;
          5222)     echo "-t xmpp " ;;   # domain of jabber server maybe needed
          5432)     echo "-t postgres " ;;
          563)                ;;  # NNTPS
          636)                ;;  # LDAP
          1443|8443|443|981)  ;;  # HTTPS
          465)                ;;  # HTTPS | SMTP
          631)                ;;  # CUPS
          853)                ;;  # DNS over TLS
          995|993)            ;;  # POP3|IMAP
          3389)               ;;  # RDP
          *) ret=1            ;;  # we don't know this ports so we rather do not scan it
     esac
     return $ret
}

nmap_to_plain_file() {
     local target_fname=""
     local oneline=""
     local ip hostdontcare round_brackets ports_specs starttls
     local tmp port host_spec protocol dontcare dontcare1
     #FIXME: IPv6 is missing here

     # Ok, since we are here we are sure to have an nmap file. To avoid questions we make sure it's the right format too
     if [[ "$(head -1 "$FNAME")" =~ ( -oG )(.*) ]] || [[ "$(head -1 "$FNAME")" =~ ( -oA )(.*) ]] ; then
          # yes, greppable
          if [[ $(grep -c Status "$FNAME") -ge 1 ]]; then
               [[ $(grep -c  '\/open\/' "$FNAME")  -eq 0 ]] && \
                    fatal "Nmap file $FNAME should contain at least one open port" $ERR_FNAMEPARSE
          else
               fatal "strange, nmap grepable misses \"Status\"" -1
          fi
     else
          fatal "Nmap file $FNAME is not in grep(p)able format (-oG filename.g(n)map)" $ERR_FNAMEPARSE
     fi
     # strip extension and create output file *.txt in same folder
     target_fname="${FNAME%.*}.txt"
     > "${target_fname}"
     if [[ $? -ne 0 ]]; then
          # try to just create ${FNAME%.*}.txt in the same dir as the gnmap file failed.
          # backup is using one in $TEMPDIR
          target_fname="${target_fname##*\/}"     # strip path (Unix)
          target_fname="${target_fname##*\\}"     # strip path (Dos)
          target_fname="$TEMPDIR/$target_fname"
          > "${target_fname}" || fatal "Cannot create \"${target_fname}\"" $ERR_FCREATE
     fi

     # Line x:   "Host: AAA.BBB.CCC.DDD (<FQDN>) Status: Up"
     # Line x+1: "Host: AAA.BBB.CCC.DDD (<FQDN>) Ports: 443/open/tcp//https///"
     # (or):      Host: AAA.BBB.CCC.DDD (<FQDN>) Ports: 22/open/tcp//ssh//<banner>/, 25/open/tcp//smtp//<banner>/, 443/open/tcp//ssl|http//<banner>
     while read -r hostdontcare ip round_brackets tmp ports_specs; do
          [[ "$ports_specs" =~ "Status: "  ]] && continue             # we don't need this
          [[ "$ports_specs" =~ '/open/tcp/' ]] || continue            # no open tcp at all for this IP --> move
          host_spec="$ip"
          fqdn="${round_brackets/\(/}"
          fqdn="${fqdn/\)/}"
          if [[ -n "$fqdn" ]]; then
               tmp="$(get_a_record "$fqdn")"
               debugme echo "$tmp \?= $ip"
               if [[ "$tmp" == "$ip" ]]; then
                    host_spec="$fqdn"
               fi
          fi
          while read -r oneline; do
               # 25/open/tcp//smtp//<banner>/,
               [[ "$oneline" =~ '/open/tcp/' ]] || continue                # no open tcp for this port on this IP --> move on
               IFS=/ read -r port dontcare protocol dontcare1 <<< "$oneline"
               starttls="$(ports2starttls $port)"
               [[ $? -eq 1 ]] && continue                                  # nmap got a port but we don't know how to speak to
               [[ "$DEBUG" -ge 1 ]] && echo "${starttls}$host_spec:$port"
               echo "${starttls}${host_spec}:${port}" >>"$target_fname"
          done < <(tr ',' '\n' <<< "$ports_specs")
     done < "$FNAME"
     [[ "$DEBUG" -ge 1 ]] && echo

     [[ -s "$target_fname" ]] || \
          fatal "Couldn't find any open port in $FNAME" $ERR_FNAMEPARSE
     export FNAME=$target_fname
}

run_mass_testing() {
     local cmdline=""
     local first=true
     local gnmapadd=""
     local saved_fname="$FNAME"

     if [[ ! -r "$FNAME" ]] && "$IKNOW_FNAME"; then
          fatal "Can't read file \"$FNAME\"" $ERR_FNAMEPARSE
     fi

     if [[ "$(head -1 "$FNAME")" =~ (Nmap [4-8])(.*)( scan initiated )(.*) ]]; then
          gnmapadd="grep(p)able nmap "
          nmap_to_plain_file
     fi

     pr_reverse "====== Running in file batch mode with ${gnmapadd}file=\"$saved_fname\" ======"; outln "\n"
     while read -r cmdline; do
          cmdline="$(filter_input "$cmdline")"
          [[ -z "$cmdline" ]] && continue
          [[ "$cmdline" == EOF ]] && break
          # Create the command line for the child in the form of an array (see #702)
          create_mass_testing_cmdline "serial" $cmdline
          draw_line "=" $((TERM_WIDTH / 2)); outln;
          outln "$(create_cmd_line_string "$0" "${MASS_TESTING_CMDLINE[@]}")"
          # we call ourselves here. $do_mass_testing is the parent, $CHILD_MASS_TESTING... you figured
          if [[ -z "$(type -p "$0")" ]]; then
               CHILD_MASS_TESTING=true "$RUN_DIR/$PROG_NAME" "${MASS_TESTING_CMDLINE[@]}"
          else
               CHILD_MASS_TESTING=true "$0" "${MASS_TESTING_CMDLINE[@]}"
          fi
          if "$JSONHEADER" && [[ -s "$TEMPDIR/jsonfile_child.json" ]]; then
               # Need to ensure that a separator is only added if the test
               # produced some JSON output.
               "$first" || fileout_separator                         # this is needed for appended output, see #687
               first=false
               cat "$TEMPDIR/jsonfile_child.json" >> "$JSONFILE"
               FIRST_FINDING=false
          fi
     done < "${FNAME}"
     return $?
}

# This function is called when it has been determined that the next child
# process has completed or it has been stopped. If the child process completed,
# then this process prints the child process's output to the terminal and, if
# appropriate, adds any JSON, CSV, and HTML output it has created to the
# appropriate file. If the child process was stopped, then a message indicating
# that is printed, but the incomplete results are not used.
#
get_next_message_testing_parallel_result() {
     draw_line "=" $((TERM_WIDTH / 2)); outln;
     outln "${PARALLEL_TESTING_CMDLINE[NEXT_PARALLEL_TEST_TO_FINISH]}"
     if [[ "$1" == completed ]]; then
          cat "$TEMPDIR/term_output_$(printf "%08d" $NEXT_PARALLEL_TEST_TO_FINISH).log"
          if "$JSONHEADER" && [[ -s "$TEMPDIR/jsonfile_$(printf "%08d" $NEXT_PARALLEL_TEST_TO_FINISH).json" ]]; then
               # Need to ensure that a separator is only added if the test
               # produced some JSON output.
               "$FIRST_JSON_OUTPUT" || fileout_separator                     # this is needed for appended output, see #687
               FIRST_JSON_OUTPUT=false
               FIRST_FINDING=false
               cat "$TEMPDIR/jsonfile_$(printf "%08d" $NEXT_PARALLEL_TEST_TO_FINISH).json" >> "$JSONFILE"
          fi
          "$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"
     elif [[ "$1" == "stopped" ]]; then
          outln "\nTest was stopped before it completed.\n"
     else
          outln "\nTest timed out before it completed.\n"
     fi
}

#FIXME: not called/tested yet
run_mass_testing_parallel() {
     local cmdline=""
     local -i i nr_active_tests=0
     local -a -i start_time=()
     local -i curr_time wait_time
     local gnmapadd=""
     local saved_fname="$FNAME"

     if [[ ! -r "$FNAME" ]] && $IKNOW_FNAME; then
          fatal "Can't read file \"$FNAME\"" $ERR_FNAMEPARSE
     fi

     if [[ "$(head -1 "$FNAME")" =~ (Nmap [4-8])(.*)( scan initiated )(.*) ]]; then
          gnmapadd="grep(p)able nmap "
          nmap_to_plain_file
     fi

     pr_reverse "====== Running in file batch mode with ${gnmapadd}file=\"$saved_fname\" ======"; outln "\n"
     while read -r cmdline; do
          cmdline="$(filter_input "$cmdline")"
          [[ -z "$cmdline" ]] && continue
          [[ "$cmdline" == "EOF" ]] && break
          # Create the command line for the child in the form of an array (see #702)
          create_mass_testing_cmdline "parallel $(printf "%08d" $NR_PARALLEL_TESTS)" $cmdline

          # fileout() won't include the "service" information in the JSON file for the child process
          # if the JSON file doesn't already exist.
          "$JSONHEADER" && >"$TEMPDIR/jsonfile_$(printf "%08d" $NR_PARALLEL_TESTS).json"
          PARALLEL_TESTING_CMDLINE[NR_PARALLEL_TESTS]="$(create_cmd_line_string "$0" "${MASS_TESTING_CMDLINE[@]}")"
          if [[ -z "$(type -p "$0")" ]]; then
               CHILD_MASS_TESTING=true "$RUN_DIR/$PROG_NAME" "${MASS_TESTING_CMDLINE[@]}" > "$TEMPDIR/term_output_$(printf "%08d" $NR_PARALLEL_TESTS).log" 2>&1 &
          else
               CHILD_MASS_TESTING=true "$0" "${MASS_TESTING_CMDLINE[@]}" > "$TEMPDIR/term_output_$(printf "%08d" $NR_PARALLEL_TESTS).log" 2>&1 &
          fi
          PARALLEL_TESTING_PID[NR_PARALLEL_TESTS]=$!
          start_time[NR_PARALLEL_TESTS]=$(date +%s)
          if "$INTERACTIVE"; then
               echo -en "\r                                                             \r" 1>&2
               echo -n "Started test #$NR_PARALLEL_TESTS" 1>&2
               [[ $NEXT_PARALLEL_TEST_TO_FINISH -lt $NR_PARALLEL_TESTS ]] && \
                    echo -n " (waiting for test #$NEXT_PARALLEL_TEST_TO_FINISH to finish)" 1>&2
          fi
          NR_PARALLEL_TESTS+=1
          nr_active_tests+=1
          sleep $PARALLEL_SLEEP
          # Get the results of any completed tests
          while [[ $NEXT_PARALLEL_TEST_TO_FINISH -lt $NR_PARALLEL_TESTS ]]; do
               if [[ ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} -eq 0 ]]; then
                    "$INTERACTIVE" && echo -en "\r                                                             \r" 1>&2
                    get_next_message_testing_parallel_result "completed"
                    NEXT_PARALLEL_TEST_TO_FINISH+=1
               elif ! ps ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} >/dev/null ; then
                    "$INTERACTIVE" && echo -en "\r                                                             \r" 1>&2
                    get_next_message_testing_parallel_result "completed"
                    NEXT_PARALLEL_TEST_TO_FINISH+=1
                    nr_active_tests=$nr_active_tests-1
               else
                    break
               fi
          done
          if [[ $nr_active_tests -ge $MAX_PARALLEL ]]; then
               curr_time=$(date +%s)
               while true; do
                    # Check to see if any test completed
                    for (( i=NEXT_PARALLEL_TEST_TO_FINISH; i < NR_PARALLEL_TESTS; i++ )); do
                         if [[ ${PARALLEL_TESTING_PID[i]} -ne 0 ]] && \
                            ! ps ${PARALLEL_TESTING_PID[i]} >/dev/null ; then
                              PARALLEL_TESTING_PID[i]=0
                              nr_active_tests=$nr_active_tests-1
                              break
                         fi
                    done
                    [[ $nr_active_tests -lt $MAX_PARALLEL ]] && break
                    if [[ $curr_time-${start_time[NEXT_PARALLEL_TEST_TO_FINISH]} -ge $MAX_WAIT_TEST ]]; then
                         # No test completed in the allocated time, so the first one to
                         # start will be killed.
                         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)
                         "$INTERACTIVE" && echo -en "\r                                                             \r" 1>&2
                         get_next_message_testing_parallel_result "timeout"
                         NEXT_PARALLEL_TEST_TO_FINISH+=1
                         nr_active_tests=$nr_active_tests-1
                         break
                    fi
                    # Wake up to increment the counter every second (so that the counter
                    # appears to users as if it is operating smoothly), but check the
                    # status of the $MAX_PARALLEL active processes less often, since the
                    # ps command is expensive.
                    for (( i=0; i <= $((MAX_PARALLEL/5)); i++ )); do
                         wait_time=$((curr_time-start_time[NEXT_PARALLEL_TEST_TO_FINISH]))
                         [[ $wait_time -gt $MAX_WAIT_TEST ]] && wait_time=$MAX_WAIT_TEST
                         if "$INTERACTIVE"; then
                              echo -en "\r                                                             \r" 1>&2
                              echo -n "Waiting for test #$NEXT_PARALLEL_TEST_TO_FINISH to finish" 1>&2
                              if [[ $((MAX_WAIT_TEST-wait_time)) -le 60 ]]; then
                                   echo -n " ($((MAX_WAIT_TEST-wait_time)) seconds to timeout)" 1>&2
                              else
                                   echo -n " ($wait_time seconds)" 1>&2
                              fi
                         fi
                         [[ $wait_time -ge $MAX_WAIT_TEST ]] && break
                         sleep 1
                         curr_time=$(date +%s)
                    done
               done
          fi
     done < "$FNAME"

     # Wait for remaining tests to finish
     curr_time=$(date +%s)
     while [[ $NEXT_PARALLEL_TEST_TO_FINISH -lt $NR_PARALLEL_TESTS ]]; do
          if [[ ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} -eq 0 ]] || \
             ! ps ${PARALLEL_TESTING_PID[NEXT_PARALLEL_TEST_TO_FINISH]} >/dev/null ; then
               "$INTERACTIVE" && echo -en "\r                                                             \r" 1>&2
               get_next_message_testing_parallel_result "completed"
               NEXT_PARALLEL_TEST_TO_FINISH+=1
          elif [[ $curr_time-${start_time[NEXT_PARALLEL_TEST_TO_FINISH]} -ge $MAX_WAIT_TEST ]]; 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)
               "$INTERACTIVE" && echo -en "\r                                                             \r" 1>&2
               get_next_message_testing_parallel_result "timeout"
               NEXT_PARALLEL_TEST_TO_FINISH+=1
          else
               # Here it is okay to check process status every second, since the
               # status of only one process is being checked.
               if "$INTERACTIVE"; then
                    echo -en "\r                                                             \r" 1>&2
                    wait_time=$((curr_time-start_time[NEXT_PARALLEL_TEST_TO_FINISH]))
                    [[ $wait_time -gt $MAX_WAIT_TEST ]] && wait_time=$MAX_WAIT_TEST
                    echo -n "Waiting for test #$NEXT_PARALLEL_TEST_TO_FINISH to finish"          1>&2
                    if [[ $((MAX_WAIT_TEST-wait_time)) -le 60 ]]; then
                         echo -n " ($((MAX_WAIT_TEST-wait_time)) seconds to timeout)" 1>&2
                    else
                         echo -n " ($wait_time seconds)" 1>&2
                    fi
               fi
               sleep 1
               curr_time=$(date +%s)
          fi
     done
     return $?
}

run_rating() {
     local final_score pre_cap_grade final_grade
     local c1_score c2_score c3_score c1_wscore c2_wscore c3_wscore
     local c1_worst c1_best
     local c3_worst c3_best c3_worst_cb c3_best_cb
     local old_ifs=$IFS sorted_reasons sorted_warnings reason_nr=0 warning_nr=0

     outln "\n";
     pr_headlineln " Rating (experimental) "
     outln

     if [[ -n "$STARTTLS_PROTOCOL" ]]; then
          pr_bold " Grade                        "; pr_svrty_critical "T"
          outln   " - STARTTLS encryption is opportunistic"
          outln   "                                  (Further details would lead to a false sense of security)"
          fileout "grade" "CRITICAL" "T"
          fileout "grade_cap_reasons" "INFO" "No more details shown as it would lead to a false sense of security"
          return 0
     fi

     # Sort the reasons. This is just nicer to read in genereal
     IFS=$'\n' sorted_reasons=($(sort -ru <<<"${GRADE_CAP_REASONS[*]}"))
     IFS=$'\n' sorted_warnings=($(sort -u <<<"${GRADE_WARNINGS[*]}"))
     IFS=$old_ifs
     pr_bold " Rating specs"; out " (not complete)  "; outln "SSL Labs's 'SSL Server Rating Guide' (version 2009q from 2020-01-30)"
     pr_bold " Specification documentation  "; pr_url "https://github.com/ssllabs/research/wiki/SSL-Server-Rating-Guide"
     outln
     fileout "rating_spec" "INFO" "SSL Labs's 'SSL Server Rating Guide' (version 2009q from 2020-01-30)"

     # No point in calculating a score, if a cap of "F", "T", or "M" has been set
     if [[ $GRADE_CAP == F || $GRADE_CAP == T || $GRADE_CAP == M ]]; then
          pr_bold " Protocol Support"; out " (weighted)  "; outln "0 (0)"
          pr_bold " Key Exchange"; out "     (weighted)  "; outln "0 (0)"
          pr_bold " Cipher Strength"; out "  (weighted)  "; outln "0 (0)"
          pr_bold " Final Score                  "; outln "0"
          pr_bold " Overall Grade                "; prln_svrty_critical "$GRADE_CAP"
          fileout "grade" "CRITICAL" "SSL grade: $GRADE_CAP"
     else
          ## Category 1
          # get best score, by searching for the best protocol, until a hit occurs
          if [[ $(has_server_protocol "tls1_3") -eq 0 || $(has_server_protocol "tls1_2") -eq 0 ]]; then
               c1_best=100
          elif [[ $(has_server_protocol "tls1_1") -eq 0 ]]; then
               c1_best=95
          elif [[ $(has_server_protocol "tls1") -eq 0 ]]; then
               c1_best=90
          elif [[ $(has_server_protocol "ssl3") -eq 0 ]]; then
               c1_best=80
               # If the best protocol offered is SSLv3, cap to F. It is easier done here
               set_grade_cap "F" "SSLv3 is the best protocol offered"
          else # SSLv2 gives 0 points
               c1_best=0
          fi

          # get worst score, by searching for the worst protcol, until a hit occurs
          if [[ $(has_server_protocol "ssl2") -eq 0 ]]; then
               c1_worst=0
          elif [[ $(has_server_protocol "ssl3") -eq 0 ]]; then
               c1_worst=80
          elif [[ $(has_server_protocol "tls1") -eq 0 ]]; then
               c1_worst=90
          elif [[ $(has_server_protocol "tls1_1") -eq 0 ]]; then
               c1_worst=95
          else # TLS1.2 and TLS1.3 both give 100 points
               c1_worst=100
          fi

          let c1_score="($c1_best+$c1_worst)/2" # Gets the category score
          let c1_wscore=$c1_score*30/100 # Gets the weighted score for category (30%)

          pr_bold " Protocol Support "; out "(weighted)  "; outln "$c1_score ($c1_wscore)"

          ## Category 2
          let c2_score=$KEY_EXCH_SCORE
          let c2_wscore=$c2_score*30/100

          pr_bold " Key Exchange "; out "    (weighted)  "; outln "$c2_score ($c2_wscore)"


          ## Category 3
          # Get the cipher bits sizes for the best cipher, and the worst cipher
          c3_best_cb=$CIPH_STR_BEST
          c3_worst_cb=$CIPH_STR_WORST

          # Determine score for the best key
          if [[ $c3_best_cb -ge 256 ]]; then
               c3_best=100
          elif [[ $c3_best_cb -ge 128 ]]; then
               c3_best=80
          elif [[ $c3_best_cb -ge 0 ]]; then
               c3_best=20
          else
               c3_best=0
          fi

          # Determine the score for the worst key
          if [[ $c3_worst_cb -gt 0 && $c3_worst_cb -lt 128 ]]; then
               c3_worst=20
          elif [[ $c3_worst_cb -lt 256 ]]; then
               c3_worst=80
          elif [[ $c3_worst_cb -ge 256 ]]; then
               c3_worst=100
          else
               c3_worst=0
          fi
          let c3_score="($c3_best+$c3_worst)/2" # Gets the category score
          let c3_wscore=$c3_score*40/100        # Gets the weighted score for category (40%)

          pr_bold " Cipher Strength "; out " (weighted)  "; outln "$c3_score ($c3_wscore)"

          ## Calculate final score and grade
          let final_score=$c1_wscore+$c2_wscore+$c3_wscore

          pr_bold " Final Score                  "; outln $final_score

          # get score, and somehow do something about the GRADE_CAP
          if [[ $final_score -ge 80 ]]; then
               pre_cap_grade="A"
          elif [[ $final_score -ge 65 ]]; then
               pre_cap_grade="B"
          elif [[ $final_score -ge 50 ]]; then
               pre_cap_grade="C"
          elif [[ $final_score -ge 35 ]]; then
               pre_cap_grade="D"
          elif [[ $final_score -ge 20 ]]; then
               pre_cap_grade="E"
          elif [[ $final_score -lt 20 ]]; then
               pre_cap_grade="F"
          fi

          # If the calculated grade is bigger than the grade cap, then set grade as the cap
          if [[ -n "$GRADE_CAP" && ! $pre_cap_grade > $GRADE_CAP ]]; then
               final_grade=$GRADE_CAP
          # For "exceptional" config, an "A+" is awarded, or "A-" for slightly less "exceptional"
          elif [[ -z "$GRADE_CAP" && $pre_cap_grade == A ]]; then
               if [[ ${#sorted_warnings[@]} -eq 0 ]]; then
                    final_grade="A+"
               else
                    final_grade="A-"
               fi
          else
               final_grade=$pre_cap_grade
          fi

          pr_bold " Overall Grade                "
          case "$final_grade" in
               A*) prln_svrty_best $final_grade
                   fileout "grade" "OK" "$final_grade"
                    ;;
               B) prln_svrty_medium $final_grade
                  fileout "grade" "MEDIUM" "$final_grade"
                    ;;
               C) prln_svrty_medium $final_grade
                  fileout "grade" "MEDIUM" "$final_grade"
                    ;;
               D) prln_svrty_high $final_grade
                  fileout "grade" "HIGH" "$final_grade"
                    ;;
               E) prln_svrty_high $final_grade
                  fileout "grade" "HIGH" "$final_grade"
                    ;;
               F) prln_svrty_critical $final_grade
                  fileout "grade" "CRITICAL" "$final_grade"
                    ;;
          esac
     fi

     # Pretty print - again, it's just nicer to read
     for reason in "${sorted_reasons[@]}"; do
          if [[ $reason_nr -eq 0 ]]; then
               pr_bold " Grade cap reasons            "; outln "$reason"
          else
               outln "                              $reason"
          fi
          let reason_nr++
          fileout "grade_cap_reason_${reason_nr}" "INFO" "$reason"
     done

     for warning in "${sorted_warnings[@]}"; do
          if [[ $warning_nr -eq 0 ]]; then
               pr_bold " Grade warning                "; prln_svrty_medium "$warning"
          else
               prln_svrty_medium "                              $warning"
          fi
          let warning_nr++
          fileout "grade_cap_warning_${warning_nr}" "INFO" "$warning"
     done

     return 0
}

# Checks whether rating can be done or not.
# Rating needs a mix of certificate and vulnerabilities checks, in order to give out proper grades.
# This function disables rating, if not all required checks are enabled
# Returns "0" if rating is enabled, and "1" if rating is disabled
set_rating_state() {
     local gbl
     local nr_enabled=0

     # All of these should be enabled
     for gbl in do_protocols do_cipherlists do_fs do_server_defaults do_header \
          do_heartbleed do_ccs_injection do_ticketbleed do_robot do_renego \
          do_crime do_ssl_poodle do_tls_fallback_scsv do_drown do_beast \
          do_rc4 do_logjam; do
          "${!gbl}" && let nr_enabled++
     done

     # ... atleast one of these has to be set
     [[ "$do_allciphers" || "$do_cipher_per_proto" ]] && let nr_enabled++

     # ... else we can't do rating
     if [[ $nr_enabled -lt 18 ]]; then
          do_rating=false
          return 1
     fi

     return 0
}


# This initializes boolean global do_* variables. They keep track of what to do
# -- as the name insinuates
initialize_globals() {
     do_allciphers=false
     do_vulnerabilities=false
     do_beast=false
     do_lucky13=false
     do_breach=false
     do_ccs_injection=false
     do_ticketbleed=false
     do_robot=false
     do_cipher_per_proto=false
     do_crime=false
     do_freak=false
     do_logjam=false
     do_drown=false
     do_header=false
     do_heartbleed=false
     do_mx_all_ips=false
     do_mass_testing=false
     do_logging=false
     do_json=false
     do_pretty_json=false
     do_csv=false
     do_html=false
     do_fs=false
     do_protocols=false
     do_rc4=false
     do_grease=false
     do_renego=false
     do_cipherlists=false
     do_server_defaults=false
     do_server_preference=false
     do_ssl_poodle=false
     do_sweet32=false
     do_tls_fallback_scsv=false
     do_cipher_match=false
     do_tls_sockets=false
     do_client_simulation=false
     do_display_only=false
     do_starttls=false
     do_rating=false
}


# Set default scanning options for the boolean global do_* variables.
set_scanning_defaults() {
     do_allciphers=false
     do_vulnerabilities=true
     do_beast=true
     do_lucky13=true
     do_breach=true
     do_heartbleed="$OFFENSIVE"
     do_ccs_injection="$OFFENSIVE"
     do_ticketbleed="$OFFENSIVE"
     do_robot="$OFFENSIVE"
     do_crime=true
     do_freak=true
     do_logjam=true
     do_drown=true
     do_ssl_poodle=true
     do_sweet32=true
     do_header=true
     do_fs=true
     do_rc4=true
     do_protocols=true
     do_renego=true
     do_cipherlists=true
     do_server_defaults=true
     do_server_preference=true
     do_tls_fallback_scsv=true
     do_client_simulation=true
     if "$OFFENSIVE"; then
          VULN_COUNT=16
     else
          VULN_COUNT=12
     fi
     do_rating=true
}

# returns number of $do variables set = number of run_funcs() to perform
count_do_variables() {
     local gbl
     local true_nr=0

     for gbl in do_allciphers do_vulnerabilities do_beast do_lucky13 do_breach do_ccs_injection do_ticketbleed do_cipher_per_proto do_crime \
               do_freak do_logjam do_drown do_header do_heartbleed do_mx_all_ips do_fs do_protocols do_rc4 do_grease do_robot do_renego \
               do_cipherlists do_server_defaults do_server_preference do_ssl_poodle do_tls_fallback_scsv \
               do_sweet32 do_client_simulation do_cipher_match do_tls_sockets do_mass_testing do_display_only do_rating; do
                    "${!gbl}" && let true_nr++
     done
     return $true_nr
}


debug_globals() {
     local gbl

     for gbl in do_allciphers do_vulnerabilities do_beast do_lucky13 do_breach do_ccs_injection do_ticketbleed do_cipher_per_proto do_crime \
               do_freak do_logjam do_drown do_header do_heartbleed do_mx_all_ips do_fs do_protocols do_rc4 do_grease do_robot do_renego \
               do_cipherlists do_server_defaults do_server_preference do_ssl_poodle do_tls_fallback_scsv \
               do_sweet32 do_client_simulation do_cipher_match do_tls_sockets do_mass_testing do_display_only do_rating; do
          printf "%-22s = %s\n" $gbl "${!gbl}"
     done
     # ${!var} is an indirect expansion, see https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html
     # Example: https://stackoverflow.com/questions/8515411/what-is-indirect-expansion-what-does-var-mean#8515492
     printf "%-22s : %s\n" URI: "$URI"
}


# This is determining the tests which should be skipped by --no-* or --disable-* a a cmdline arg.
# It achieves that by setting the do_<variables> according to the global array $SKIP_TESTS
#
set_skip_tests() {
     local t

     for t in ${SKIP_TESTS[@]} ; do
          t="do_${t}"
          # declare won't do it here --> local scope
          eval "$t"=false
          debugme printf '%s\n' "set $t: ${!t}"
     done
}



# arg1: either switch+value (=) or switch
# arg2: value (if no = provided)
parse_opt_equal_sign() {
     if [[ "$1" == *=* ]]; then
          echo ${1#*=}
          return 1  # = means we don't need to shift args!
     else
          echo "$2"
          return 0  # we need to shift
     fi
}

# Create the command line string for printing purposes
# See https://stackoverflow.com/questions/10835933/preserve-quotes-in-bash-arguments
create_cmd_line_string() {
     local arg
     local -a allargs=()
     local chars='[ !"#$&()*,;<>?\^`{|}]'

     while [[ $# -gt 0 ]]; do
          if [[ $1 == *\'* ]]; then
               arg=\""$1"\"
          elif [[ $1 == *$chars* ]]; then
               arg="'$1'"
          else
               arg="$1"
          fi
          allargs+=("$arg")    # ${allargs[@]} is to be used only for printing
          shift
     done
     printf '%s\n' "${allargs[*]}"
}

check_base_requirements() {
     local binary=''
     local whitelist=' hexdump grep awk sed '

     for binary in 'hexdump' 'dd' 'grep' 'awk' 'tr' 'sed' 'wc' 'date' 'cat' 'ps' 'kill' 'head' 'tail' 'dirname';  do
          if ! type -p "${binary}" &> /dev/null; then
               fatal "You need to install ${binary} for this program to work" $ERR_RESOURCE
          fi
          [[ ${whitelist} =~ \ ${binary}\  ]] && continue
          "${binary}" --help 2>&1 | grep -iq busybox
          if [[ $? -eq 0 ]]; then
               fatal "${binary} is from busybox. Please install a regular binary" $ERR_RESOURCE
          fi
     done
}

parse_cmd_line() {
     local outfile_arg=""
     local cipher_mapping
     local -i subret=0

     CMDLINE="$(create_cmd_line_string "${CMDLINE_ARRAY[@]}")"
     CMDLINE_PARSED=false

     case $1 in
          --help|"")
               help 0
               ;;
          -b|--banner|-v|--version)
               maketempf
               get_install_dir
               find_openssl_binary
               prepare_debug
               mybanner
               exit $ALLOK
               ;;
     esac

     # set all do_* globals to false
     initialize_globals

     while [[ $# -gt 0 ]]; do
          case $1 in
               --mx)
                    do_mx_all_ips=true
                    PORT=25
                    ;;
               --mx465)                      # doesn't work with major ISPs
                    do_mx_all_ips=true
                    PORT=465
                    ;;
               --mx587)                      # doesn't work with major ISPs
                    do_mx_all_ips=true
                    PORT=587
                    ;;
               --ip|--ip=*)
                    CMDLINE_IP="$(parse_opt_equal_sign "$1" "$2")"
                    [[ $? -eq 0 ]] && shift
                    if [[ "$CMDLINE_IP" == proxy ]]; then
                         DNS_VIA_PROXY=true
                         unset CMDLINE_IP
                    fi
                    # normalize any IPv6 address
                    CMDLINE_IP="${CMDLINE_IP//[/}"      # fix vim syntax highlighting "]
                    CMDLINE_IP="${CMDLINE_IP//]/}"
                    ;;
               -n|--nodns|-n=*|--nodns=*)
                    NODNS="$(parse_opt_equal_sign "$1" "$2")"
                    [[ $? -eq 0 ]] && shift
                    if [[ "$NODNS" != none ]] && [[ "$NODNS" != min ]]; then
                         fatal "Value for nodns switch can be either \"min\" or \"none\"" $ERR_CMDLINE
                    fi
                    ;;
               -V|-V=*|--local|--local=*)    # attention, this could have a value or not!
                    do_display_only=true
                    PATTERN2SHOW="$(parse_opt_equal_sign "$1" "$2")"
                    subret=$?
                    if [[ "$PATTERN2SHOW" == -* ]]; then
                         unset PATTERN2SHOW  # we hit the next command ==> not our value
                    else                     # it was ours, point to next arg
                         [[ $subret -eq 0 ]] && shift
                    fi
                    ;;
               -x|-x=*|--single[-_]cipher|--single[-_]cipher=*)
                    do_cipher_match=true
                    single_cipher=$(parse_opt_equal_sign "$1" "$2")
                    [[ $? -eq 0 ]] && shift
                    ;;
               -t|-t=*|--starttls|--starttls=*)
                    do_starttls=true
                    STARTTLS_PROTOCOL="$(parse_opt_equal_sign "$1" "$2")"
                    [[ $? -eq 0 ]] && shift
                    case $STARTTLS_PROTOCOL in
                         ftp|smtp|lmtp|pop3|imap|xmpp|xmpp-server|telnet|ldap|irc|nntp|postgres|mysql) ;;
                         ftps|smtps|lmtps|pop3s|imaps|xmpps|telnets|ldaps|ircs|nntps|mysqls) ;;
                         *)   tmln_magenta "\nunrecognized STARTTLS protocol \"$1\", see help" 1>&2
                              help 1 ;;
                    esac
                    ;;
               --xmpphost|--xmpphost=*)
                    XMPP_HOST=$(parse_opt_equal_sign "$1" "$2")
                    [[ $? -eq 0 ]] && shift
                    ;;
               -e|--each-cipher)
                    do_allciphers=true
                    ;;
               -E|--cipher-per-proto|--cipher_per_proto)
                    do_cipher_per_proto=true
                    ;;
               -p|--protocols)
                    do_protocols=true
                    ;;
               -s|--std|--standard)
                    do_cipherlists=true
                    ;;
               -S|--server[-_]defaults)
                    do_server_defaults=true
                    ;;
               -P|--server[_-]preference|--preference)
                    do_server_preference=true
                    ;;
               -h|--header|--headers)
                    do_header=true
                    ;;
               -c|--client-simulation)
                    do_client_simulation=true
                    ;;
               -U|--vulnerable|--vulnerabilities)
                    do_vulnerabilities=true
                    do_heartbleed="$OFFENSIVE"
                    do_ccs_injection="$OFFENSIVE"
                    do_ticketbleed="$OFFENSIVE"
                    do_robot="$OFFENSIVE"
                    do_renego=true
                    do_crime=true
                    do_breach=true
                    do_ssl_poodle=true
                    do_tls_fallback_scsv=true
                    do_sweet32=true
                    do_freak=true
                    do_drown=true
                    do_logjam=true
                    do_beast=true
                    do_lucky13=true
                    do_rc4=true
                    if "$OFFENSIVE"; then
                         VULN_COUNT=16
                    else
                         VULN_COUNT=12
                    fi
                    ;;
               --ids-friendly)
                    OFFENSIVE=false
                    ;;
               -H|--heartbleed)
                    do_heartbleed=true
                    let "VULN_COUNT++"
                    ;;
               -I|--ccs|--ccs[-_]injection)
                    do_ccs_injection=true
                    let "VULN_COUNT++"
                    ;;
               -T|--ticketbleed)
                    do_ticketbleed=true
                    let "VULN_COUNT++"
                    ;;
               -BB|--robot)
                    do_robot=true
                    ;;
               -R|--renegotiation)
                    do_renego=true
                    let "VULN_COUNT++"
                    ;;
               -C|--compression|--crime)
                    do_crime=true
                    let "VULN_COUNT++"
                    ;;
               -B|--breach)
                    do_breach=true
                    let "VULN_COUNT++"
                    ;;
               -O|--poodle)
                    do_ssl_poodle=true
                    do_tls_fallback_scsv=true
                    let "VULN_COUNT++"
                    ;;
               -Z|--tls[_-]fallback|tls[_-]fallback[_-]scs)
                    do_tls_fallback_scsv=true
                    let "VULN_COUNT++"
                    ;;
               -W|--sweet32)
                    do_sweet32=true
                    let "VULN_COUNT++"
                    ;;
               -F|--freak)
                    do_freak=true
                    let "VULN_COUNT++"
                    ;;
               -D|--drown)
                    do_drown=true
                    let "VULN_COUNT++"
                    ;;
               -J|--logjam)
                    do_logjam=true
                    let "VULN_COUNT++"
                    ;;
               -A|--beast)
                    do_beast=true
                    let "VULN_COUNT++"
                    ;;
               -L|--lucky13)
                    do_lucky13=true
                    let "VULN_COUNT++"
                    ;;
               -4|--rc4|--appelbaum)
                    do_rc4=true
                    let "VULN_COUNT++"
                    ;;
               -f|--fs|--nsa|--forward-secrecy)
                    do_fs=true
                    ;;
               -g|--grease)
                    do_grease=true
                    ;;
               --disable-rating|--no-rating)
                    SKIP_TESTS+=("rating")
                    # TODO: a generic thing would be --disable-* / --no-* ,
                    # catch $1 and add it to the array ( #1502 )
                    ;;
               -9|--full)
                    set_scanning_defaults
                    do_allciphers=false
                    do_cipher_per_proto=true
                    do_grease=true
                    ;;
               --add-ca|--add-CA|--add-ca=*|--add-CA=*)
                    ADDTL_CA_FILES="$(parse_opt_equal_sign "$1" "$2")"
                    [[ $? -eq 0 ]] && shift
                    ;;
               --devel) echo -e "\nthis is a development feature and may disappear at any time"
                    # arg1: SSL/TLS protocol (SSLv2=22)
                    # arg2: list of cipher suites / hostname/ip
                    # arg3: hostname/ip
                    # DEBUG=3  ./testssl.sh --devel 04 "13,02, 13,01" google.com                                --> TLS 1.3
                    # DEBUG=3  ./testssl.sh --devel 03 "cc, 13, c0, 13" google.de                               --> TLS 1.2, old CHACHA/POLY
                    # DEBUG=3  ./testssl.sh --devel 03 "cc,a8, cc,a9, cc,aa, cc,ab, cc,ac" blog.cloudflare.com  -->          new CHACHA/POLY
                    # DEBUG=3  ./testssl.sh --devel 01 yandex.ru                                                --> TLS 1.0
                    # DEBUG=3  ./testssl.sh --devel 00 <host which supports SSLv3>
                    # DEBUG=3  ./testssl.sh --devel 22 <host which still supports SSLv2>
                    HEX_CIPHER="$TLS12_CIPHER"
                    TLS_LOW_BYTE="$2"
                    if [[ $# -eq 4 ]]; then  # protocol AND ciphers specified
                         HEX_CIPHER="$3"
                         shift
                    fi
                    shift
                    do_tls_sockets=true
                    outln "\nTLS_LOW_BYTE, HEX_CIPHER: \"${TLS_LOW_BYTE}\", \"${HEX_CIPHER}\""
                    ;;
               --wide)
                    WIDE=true
                    ;;
               --assuming[_-]http|--assume[-_]http)
                    ASSUME_HTTP=true
                    ;;
               --sneaky)
                    SNEAKY=true
                    ;;
               -q|--quiet)
                    QUIET=true
                    ;;
               --file|--file=*|-iL|-iL=*)
                    # no shift here as otherwise URI is empty and it bails out
                    FNAME="$(parse_opt_equal_sign "$1" "$2")"
                    [[ $? -eq 0 ]] && shift
                    IKNOW_FNAME=true
                    WARNINGS=batch           # set this implicitly!
                    do_mass_testing=true
                    ;;
               --mode|--mode=*)
                    MASS_TESTING_MODE="$(parse_opt_equal_sign "$1" "$2")"
                    [[ $? -eq 0 ]] && shift
                    case "$MASS_TESTING_MODE" in
                         serial|parallel) ;;
                         *)   tmln_magenta "\nmass testing mode can be either \"serial\" or \"parallel\""
                              help 1
                    esac
                    ;;
               --serial)
                    MASS_TESTING_MODE=serial
                    ;;
               --parallel)
                    MASS_TESTING_MODE=parallel
                    ;;
               --warnings|--warnings=*)
                    WARNINGS=$(parse_opt_equal_sign "$1" "$2")
                    [[ $? -eq 0 ]] && shift
                    case "$WARNINGS" in
                         batch|off) ;;
                         *)   tmln_magenta "\nwarnings can be either \"batch\", or \"off\""
                              help 1
                    esac
                    ;;
               --show[-_]each)
                    SHOW_EACH_C=true
                    ;;
               --fast)
                    FAST=true
                    ;;
               --bugs)
                    BUGS="-bugs"
                    ;;
               --debug|--debug=*)
                    DEBUG=$(parse_opt_equal_sign "$1" "$2")
                    [[ $? -eq 0 ]] && shift
                    case $DEBUG in
                         [0-6]) ;;
                         *)   tmln_magenta_term "\nunrecognized debug value \"$1\", must be between 0..6" 1>&2
                              help 1
                    esac
                    ;;
               --color|--color=*)
                    COLOR="$(parse_opt_equal_sign "$1" "$2")"
                    [[ $? -eq 0 ]] && shift
                    case $COLOR in
                         [0-3]) ;;
                         *)   COLOR=2
                              tmln_magenta "\nunrecognized color: \"$1\", must be between 0..3" 1>&2
                              help 1
                    esac
                    ;;
               --colorblind)
                    COLORBLIND=true
                    ;;
               --log|--logging)
                    "$do_logging" && fatal "two --log* arguments" $ERR_CMDLINE
                    do_logging=true
                    ;;   # DEFINITION of LOGFILE if no arg specified: automagically in parse_hn_port()
                    # following does the same but additionally we can specify a log location
               --logfile|--logfile=*|-oL|-oL=*)
                    "$do_logging" && fatal "two --log* arguments" $ERR_CMDLINE
                    LOGFILE="$(parse_opt_equal_sign "$1" "$2")"
                    [[ $? -eq 0 ]] && shift
                    do_logging=true
                    ;;
               --json)
                    "$do_pretty_json" && fatal "flat and pretty JSON output are mutually exclusive" $ERR_CMDLINE
                    "$do_json" && fatal "--json and --jsonfile are mutually exclusive" $ERR_CMDLINE
                    if [[ "$2" =~ \.(json|JSON)$ ]]; then
                         fatal "No file name allowed after \"--json\" (use \"--jsonfile\" instead)" $ERR_CMDLINE
                    fi
                    do_json=true
                    ;;   # DEFINITION of JSONFILE is not arg specified: automagically in parse_hn_port()
                    # following does the same but additionally we can specify a log location
               --jsonfile|--jsonfile=*|-oj|-oj=*)
                    "$do_pretty_json"  && fatal "flat and pretty JSON output are mutually exclusive" $ERR_CMDLINE
                    "$do_json" && fatal "--json and --jsonfile are mutually exclusive" $ERR_CMDLINE
                    JSONFILE="$(parse_opt_equal_sign "$1" "$2")"
                    [[ $? -eq 0 ]] && shift
                    do_json=true
                    ;;
               --json-pretty)
                    "$do_json" && fatal "flat and pretty JSON output are mutually exclusive" $ERR_CMDLINE
                    "$do_pretty_json" && fatal "--json-pretty and --jsonfile-pretty are mutually exclusive" $ERR_CMDLINE
                    if [[ "$2" =~ \.(json|JSON)$ ]]; then
                         fatal "No file name allowed after \"--json\" (use \"--jsonfile-pretty\" instead)" $ERR_CMDLINE
                    fi
                    do_pretty_json=true
                    ;;
               --jsonfile-pretty|--jsonfile-pretty=*|-oJ|-oJ=*)
                    "$do_json" && fatal "flat and pretty JSON output are mutually exclusive" $ERR_CMDLINE
                    "$do_pretty_json" && fatal "--json-pretty and --jsonfile-pretty are mutually exclusive" $ERR_CMDLINE
                    JSONFILE="$(parse_opt_equal_sign "$1" "$2")"
                    [[ $? -eq 0 ]] && shift
                    do_pretty_json=true
                    ;;
               --severity|--severity=*)
                    set_severity_level "$(parse_opt_equal_sign "$1" "$2")"
                    [[ $? -eq 0 ]] && shift
                    ;;
               --hints)
                    GIVE_HINTS=true
                    ;;
               --csv)
                    "$do_csv" && fatal "two --csv* arguments" $ERR_CMDLINE
                    if [[ "$2" =~ \.(csv|CSV)$ ]]; then
                         fatal "No file name allowed after \"--csv\" (use \"--csvfile\" instead)" $ERR_CMDLINE
                    fi
                    do_csv=true
                    ;;   # DEFINITION of CSVFILE is not arg specified: automagically in parse_hn_port()
                    # following does the same but additionally we can specify a log location
               --csvfile|--csvfile=*|-oC|-oC=*)
                    "$do_csv" && fatal "two --csv* arguments" $ERR_CMDLINE
                    CSVFILE="$(parse_opt_equal_sign "$1" "$2")"
                    [[ $? -eq 0 ]] && shift
                    do_csv=true
                    ;;
               --html)
                    "$do_html" && fatal "two --html* arguments" $ERR_CMDLINE
                    if [[ "$2" =~ \.(htm|html|HTM|HTML)$ ]]; then
                         fatal "No file name allowed after \"--html\" (use \"--htmlfile\" instead)" $ERR_CMDLINE
                    fi
                    do_html=true
                    ;;  # DEFINITION of HTMLFILE is not arg specified: automagically in parse_hn_port()
                    # following does the same but additionally we can specify a file location
               --htmlfile|--htmlfile=*|-oH|-oH=*)
                    "$do_html" && fatal "two --html* arguments" $ERR_CMDLINE
                    HTMLFILE="$(parse_opt_equal_sign "$1" "$2")"
                    [[ $? -eq 0 ]] && shift
                    do_html=true
                    ;;
               --outfile|--outfile=*|-oa|-oa=*)
                    ( "$do_html" || "$do_json" || "$do_pretty_json" || "$do_csv" || "$do_logging" ) && fatal "check your arguments four multiple file output options" $ERR_CMDLINE
                    outfile_arg="$(parse_opt_equal_sign "$1" "$2")"
                    if [[ "$outfile_arg" != "auto" ]]; then
                         if [[ -d "$outfile_arg" ]]; then
                              HTMLFILE="$outfile_arg"
                              CSVFILE="$outfile_arg"
                              JSONFILE="$outfile_arg"
                              LOGFILE="$outfile_arg"
                         else
                              HTMLFILE="$outfile_arg.html"
                              CSVFILE="$outfile_arg.csv"
                              JSONFILE="$outfile_arg.json"
                              LOGFILE="$outfile_arg.log"
                         fi
                    fi
                    [[ $? -eq 0 ]] && shift
                    do_html=true
                    do_json=true
                    do_csv=true
                    do_logging=true
                    ;;
               --outFile|--outFile=*|-oA|-oA=*)
                    ( "$do_html" || "$do_json" || "$do_pretty_json" || "$do_csv" || "$do_logging" ) && fatal "check your arguments four multiple file output options" $ERR_CMDLINE
                    outfile_arg="$(parse_opt_equal_sign "$1" "$2")"
                    if [[ "$outfile_arg" != "auto" ]]; then
                         if [[ -d "$outfile_arg" ]]; then
                              HTMLFILE="$outfile_arg"
                              CSVFILE="$outfile_arg"
                              JSONFILE="$outfile_arg"
                              LOGFILE="$outfile_arg"
                         else
                              HTMLFILE="$outfile_arg.html"
                              CSVFILE="$outfile_arg.csv"
                              JSONFILE="$outfile_arg.json"
                              LOGFILE="$outfile_arg.log"
                         fi
                    fi
                    [[ $? -eq 0 ]] && shift
                    do_html=true
                    do_pretty_json=true
                    do_csv=true
                    do_logging=true
                    ;;
               --append)
                    APPEND=true
                    ;;
               --outprefix)
                    FNAME_PREFIX="$(parse_opt_equal_sign "$1" "$2")"
                    if [[ $? -eq 0 ]]; then
                         shift
                         case "$(get_last_char "$FNAME_PREFIX")" in
                              '.') ;;
                              '-') ;;
                              '_') ;;
                              *) FNAME_PREFIX="${FNAME_PREFIX}-" ;;
                         esac
                    fi
                    ;;
               --openssl|--openssl=*)
                    OPENSSL="$(parse_opt_equal_sign "$1" "$2")"
                    [[ $? -eq 0 ]] && shift
                    ;;
               --openssl-timeout|--openssl-timeout=*)
                    OPENSSL_TIMEOUT="$(parse_opt_equal_sign "$1" "$2")"
                    [[ $? -eq 0 ]] && shift
                    ;;
               --connect-timeout|--connect-timeout=*)
                    CONNECT_TIMEOUT="$(parse_opt_equal_sign "$1" "$2")"
                    [[ $? -eq 0 ]] && shift
                    ;;
               --mapping|--mapping=*)
                    cipher_mapping="$(parse_opt_equal_sign "$1" "$2")"
                    [[ $? -eq 0 ]] && shift
                    case "$cipher_mapping" in
                         no-openssl) DISPLAY_CIPHERNAMES="rfc-only" ;;
                         no-rfc|no-iana) DISPLAY_CIPHERNAMES="openssl-only" ;;
                         openssl) DISPLAY_CIPHERNAMES="openssl" ;;
                         rfc|iana) DISPLAY_CIPHERNAMES="rfc" ;;
                         *)   tmln_warning "\nmapping can only be \"no-openssl\", \"no-iana\"(\"no-rfc\"), \"openssl\" or \"iana\"(\"rfc\")"
                              help 1 ;;
                    esac
                    ;;
               --proxy|--proxy=*)
                    PROXY="$(parse_opt_equal_sign "$1" "$2")"
                    [[ $? -eq 0 ]] && shift
                    ;;
               --phone-out)
                    PHONE_OUT=true
                    ;;
               -6)  # doesn't work automagically. My versions have -DOPENSSL_USE_IPV6, CentOS/RHEL/FC do not
                    HAS_IPv6=true
                    ;;
               --has[-_]dhbits|--has[_-]dh[-_]bits)
                    # Should work automagically. Helper switch for CentOS,RHEL+FC w openssl server temp key backport (version 1.0.1), see #190
                    HAS_DH_BITS=true
                    ;;
               --ssl_native|--ssl-native)
                    SSL_NATIVE=true
                    ;;
               --basicauth|--basicauth=*)
                    BASICAUTH="$(parse_opt_equal_sign "$1" "$2")"
                    [[ $? -eq 0 ]] && shift
                    ;;
               (--) shift
                    break
                    ;;
               (-*) tmln_warning "0: unrecognized option \"$1\"" 1>&2;
                    help 1
                    ;;
               (*)  break
                    ;;
          esac
          shift
     done

     # Show usage if no further options were specified
     if [[ -z "$1" ]] && [[ -z "$FNAME" ]] && ! "$do_display_only"; then
          fatal "URI missing" $ERR_CMDLINE
     else
     # What is left here should be the URI.
          URI="$1"
          [[ -n "$2" ]] && fatal "URI comes last" $ERR_CMDLINE
     fi

     # Now spot some incompatibilities in cmdlines
     [[ $CMDLINE_IP == one ]] && [[ "$NODNS" == none ]] && fatal "\"--ip=one\" and \"--nodns=none\" don't work together" $ERR_CMDLINE
     [[ $CMDLINE_IP == one ]] && ( is_ipv4addr "$URI" || is_ipv6addr "$URI" )  && fatal "\"--ip=one\" plus supplying an IP address doesn't work" $ERR_CMDLINE
     "$do_mx_all_ips" && [[ "$NODNS" == none ]] && fatal "\"--mx\" and \"--nodns=none\" don't work together" $ERR_CMDLINE
     [[ -n "$CONNECT_TIMEOUT" ]] && [[ "$MASS_TESTING_MODE" == parallel ]] && fatal "Parallel mass scanning and specifying connect timeouts currently don't work together" $ERR_CMDLINE

     ADDTL_CA_FILES="${ADDTL_CA_FILES//,/ }"
     for fname in $ADDTL_CA_FILES; do
          [[ -s "$fname" ]] || fatal "CA file \"$fname\" does not exist" $ERR_RESOURCE
          grep -q "BEGIN CERTIFICATE" "$fname" || fatal "\"$fname\" is not CA file in PEM format" $ERR_RESOURCE
     done

     count_do_variables
     [[ $? -eq 0 ]] && set_scanning_defaults
     set_skip_tests
     [[ "$DEBUG" -ge 5 ]] && debug_globals

     # Unless explicit disabled, check if rating can be enabled
     # Should be called after set_scanning_defaults
     ! "$do_rating" && set_rating_state

     CMDLINE_PARSED=true
}


# connect call from openssl needs ipv6 in square brackets
nodeip_to_proper_ip6() {
     local len_nodeip=0

     if is_ipv6addr $NODEIP; then
          ${UNBRACKTD_IPV6} || NODEIP="[$NODEIP]"
          len_nodeip=${#NODEIP}
          CORRECT_SPACES="$(printf -- " "'%.s' $(eval "echo {1.."$((len_nodeip - 17))"}"))"
          # IPv6 addresses are longer, this variable takes care that "further IP" and "Service" is properly aligned
     fi
}


reset_hostdepended_vars() {
     TLS_EXTENSIONS=""
     PROTOS_OFFERED=""
     CURVES_OFFERED=""
     OPTIMAL_PROTO=""
     ALL_FAILED_SOCKETS=true
     SERVER_SIZE_LIMIT_BUG=false
}

# Rough estimate, in the future we maybe want to make use of nano secs (%N). Note this
# is for performance debugging purposes (MEASURE_TIME=yes), eye candy is not important.
#
stopwatch() {
     local new_delta
     local column=$((COLUMNS - 0))           # for future adjustments

     "$MEASURE_TIME" || return
     new_delta=$(( $(date +%s) - LAST_TIME ))
     printf "%${column}s" "$1: $new_delta"
     [[ -e "$MEASURE_TIME_FILE" ]] && echo "$1 : $new_delta " >> "$MEASURE_TIME_FILE"
     LAST_TIME=$(( new_delta + LAST_TIME ))
}


# arg1(optional): "init" --> just initializing. Or: STARTTLS protocol
lets_roll() {
     local -i ret=0
     local section_number=0

     if [[ "$1" == init ]]; then
          # called once upfront to be able to measure preparation time b4 everything starts
          START_TIME=$(date +%s)
          LAST_TIME=$START_TIME
          [[ -n "$MEASURE_TIME_FILE" ]] && >"$MEASURE_TIME_FILE"
          return 0
     fi
     stopwatch initialized

     [[ -z "$NODEIP" ]] && fatal "$NODE doesn't resolve to an IP address" $ERR_DNSLOOKUP
     nodeip_to_proper_ip6
     reset_hostdepended_vars
     determine_rdns                # Returns always zero or has already exited if fatal error occurred
     stopwatch determine_rdns

     ((SERVER_COUNTER++))
     datebanner " Start"
     determine_service "$1"        # STARTTLS service? Other will be determined here too. Returns 0 if test connect was ok or has already exited if fatal error occurred
                                   # determine_service() can return 1, it indicates that this IP cannot be reached but there are more IPs to check
     if [[ $? -eq 0 ]] ; then
          # "secret" devel options --devel:
          if "$do_tls_sockets"; then
               if [[ "$TLS_LOW_BYTE" == 22 ]]; then
                    sslv2_sockets "" "true"
               else
                    if [[ "$TLS_LOW_BYTE" == 04 ]]; then
                         if "$CERT_COMPRESSION"; then
                              # See PR #1279
                              [[ $DEBUG -eq 3 ]] && tmln_out "including TLS extension certificate compression"
                              tls_sockets "$TLS_LOW_BYTE" "$HEX_CIPHER" "all+" "00,1b, 00,03, 02, 00,01"
                         else
                              tls_sockets "$TLS_LOW_BYTE" "$HEX_CIPHER" "ephemeralkey"
                         fi
                    else
                         tls_sockets "$TLS_LOW_BYTE" "$HEX_CIPHER" "all"
                    fi
               fi
               echo $?
               exit $ALLOK;
          fi
          if "$do_cipher_match"; then
               # we will have an invalid JSON with no if statement
               fileout_section_header $section_number false
               run_cipher_match ${single_cipher}
               stopwatch run_cipher_match
          else
               fileout_section_header $section_number false && ((section_number++))
               determine_sizelimitbug
               fileout_section_footer false

               ((section_number++))
               # all top level functions now following have the prefix "run_"
               fileout_section_header $section_number false && ((section_number++))
               "$do_protocols" && {
                    run_protocols; ret=$(($? + ret)); stopwatch run_protocols;
                    run_npn; ret=$(($? + ret)); stopwatch run_npn;
                    run_alpn; ret=$(($? + ret)); stopwatch run_alpn;
               }
               fileout_section_header $section_number true && ((section_number++))
               "$do_grease" && { run_grease; ret=$(($? + ret)); stopwatch run_grease; }

               fileout_section_header $section_number true && ((section_number++))
               "$do_cipherlists" && { run_cipherlists; ret=$(($? + ret)); stopwatch run_cipherlists; }

              fileout_section_header $section_number true && ((section_number++))
               "$do_server_preference" && { run_server_preference; ret=$(($? + ret)); stopwatch run_server_preference; }

               fileout_section_header $section_number true && ((section_number++))
               "$do_fs" && { run_fs; ret=$(($? + ret)); stopwatch run_fs; }

               fileout_section_header $section_number true && ((section_number++))
               "$do_server_defaults" && { run_server_defaults; ret=$(($? + ret)); stopwatch run_server_defaults; }

               if "$do_header"; then
                    #TODO: refactor this into functions
                    fileout_section_header $section_number true && ((section_number++))
                         if [[ $SERVICE == HTTP ]]; then
                              run_http_header "$URL_PATH";       ret=$(($? + ret))
                              run_http_date "$URL_PATH";         ret=$(($? + ret))
                              run_hsts "$URL_PATH";              ret=$(($? + ret))
                              run_hpkp "$URL_PATH";              ret=$(($? + ret))
                              run_server_banner "$URL_PATH";     ret=$(($? + ret))
                              run_appl_banner "$URL_PATH";       ret=$(($? + ret))
                              run_cookie_flags "$URL_PATH";      ret=$(($? + ret))
                              run_security_headers "$URL_PATH";  ret=$(($? + ret))
                              run_rp_banner "$URL_PATH";         ret=$(($? + ret))
                              stopwatch do_header
                         fi
               else
                    ((section_number++))
               fi

               # vulnerabilities
               if [[ $VULN_COUNT -gt $VULN_THRESHLD ]] || "$do_vulnerabilities"; then
                    outln; pr_headlineln " Testing vulnerabilities "
                    outln
               fi

               fileout_section_header $section_number true && ((section_number++))
               "$do_heartbleed" && { run_heartbleed; ret=$(($? + ret)); stopwatch run_heartbleed; }
               "$do_ccs_injection" && { run_ccs_injection; ret=$(($? + ret)); stopwatch run_ccs_injection; }
               "$do_ticketbleed" && { run_ticketbleed; ret=$(($? + ret)); stopwatch run_ticketbleed; }
               "$do_robot" && { run_robot; ret=$(($? + ret)); stopwatch run_robot; }
               "$do_renego" && { run_renego; ret=$(($? + ret)); stopwatch run_renego; }
               "$do_crime" && { run_crime; ret=$(($? + ret)); stopwatch run_crime; }
               "$do_breach" && { run_breach "$URL_PATH" ; ret=$(($? + ret));  stopwatch run_breach; }
               "$do_ssl_poodle" && { run_ssl_poodle; ret=$(($? + ret)); stopwatch run_ssl_poodle; }
               "$do_tls_fallback_scsv" && { run_tls_fallback_scsv; ret=$(($? + ret)); stopwatch run_tls_fallback_scsv; }
               "$do_sweet32" && { run_sweet32; ret=$(($? + ret)); stopwatch run_sweet32; }
               "$do_freak" && { run_freak; ret=$(($? + ret)); stopwatch run_freak; }
               "$do_drown" && { run_drown ret=$(($? + ret)); stopwatch run_drown; }
               "$do_logjam" && { run_logjam; ret=$(($? + ret)); stopwatch run_logjam; }
               "$do_beast" && { run_beast; ret=$(($? + ret)); stopwatch run_beast; }
               "$do_lucky13" && { run_lucky13; ret=$(($? + ret)); stopwatch run_lucky13; }
               "$do_rc4" && { run_rc4; ret=$(($? + ret)); stopwatch run_rc4; }

               fileout_section_header $section_number true && ((section_number++))
               "$do_allciphers" && { run_allciphers; ret=$(($? + ret)); stopwatch run_allciphers; }
               "$do_cipher_per_proto" && { run_cipher_per_proto; ret=$(($? + ret)); stopwatch run_cipher_per_proto; }

               fileout_section_header $section_number true && ((section_number++))
               "$do_client_simulation" && { run_client_simulation; ret=$(($? + ret)); stopwatch run_client_simulation; }

               fileout_section_header $section_number true && ((section_number++))
               "$do_rating" && { run_rating; ret=$(($? + ret)); stopwatch run_rating; }

          fi
          fileout_section_footer true
     fi

     outln
     calc_scantime
     datebanner " Done"

     # reset the failed connect counter as we are finished
     NR_SOCKET_FAIL=0
     NR_OSSL_FAIL=0

     "$MEASURE_TIME" && printf "$1: %${COLUMNS}s\n" "$SCAN_TIME"
     [[ -e "$MEASURE_TIME_FILE" ]] && echo "Total : $SCAN_TIME " >> "$MEASURE_TIME_FILE"

     return $ret
}



################# main #################


     RET=0     # this is a global as we can have a function main(), see #705. Should we toss then all local $ret?
     ip=""
     stopwatch start

     lets_roll init
     initialize_globals
     check_base_requirements            # needs to come after $do_html is defined
     parse_cmd_line "$@"
     # CMDLINE_PARSED has been set now. Don't put a function immediately after this which calls fatal().
     # Rather put it after csv_header below.
     # html_header() needs to be called early! Otherwise if html_out() is called before html_header() and the
     # command line contains --htmlfile <htmlfile> or --html, it'll make problems with html output, see #692.
     # json_header and csv_header could be called later but for context reasons we'll leave it here
     html_header
     json_header
     csv_header
     get_install_dir
     # see #705, we need to source TLS_DATA_FILE here instead of in get_install_dir(), see #705
     [[ -r "$TLS_DATA_FILE" ]] && . "$TLS_DATA_FILE"
     set_color_functions
     maketempf
     find_openssl_binary
     choose_printf
     check_resolver_bins
     prepare_debug  ; stopwatch parse
     prepare_arrays ; stopwatch prepare_arrays
     mybanner
     check_proxy
     check4openssl_oldfarts
     check_bsd_mount


     if "$do_display_only"; then
          prettyprint_local "$PATTERN2SHOW"
          exit $?
     fi
     fileout_banner

     if "$do_mass_testing"; then
          prepare_logging
          if [[ "$MASS_TESTING_MODE" == parallel ]]; then
               run_mass_testing_parallel
          else
               run_mass_testing
          fi
          exit $?
     fi
     html_banner

     #TODO: there shouldn't be the need for a special case for --mx, only the ip addresses we would need upfront and the do-parser
     if "$do_mx_all_ips"; then
          #FIXME: do we need this really here?
          count_do_variables                           # 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 what's necessary as below we have similar code
          exit $?
     fi

     [[ -z "$NODE" ]] && parse_hn_port "${URI}"        # NODE, URL_PATH, PORT, IPADDRs and IP46ADDR is set now
     prepare_logging

     if ! determine_ip_addresses; then
          fatal "No IP address could be determined" $ERR_DNSLOOKUP
     fi
     if [[ $(count_words "$IPADDRs") -gt 1 ]]; then    # we have more than one ipv4 address to check
          MULTIPLE_CHECKS=true
          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 + $?))                       # RET value per IP address
          done
          draw_line "-" $((TERM_WIDTH * 2 / 3))
          outln
          pr_bold "Done testing now all IP addresses (on port $PORT): "; outln "$IPADDRs"
     else                                              # Just 1x ip4v to check, applies also if CMDLINE_IP was supplied
          NODEIP="$IPADDRs"
          lets_roll "${STARTTLS_PROTOCOL}"
          RET=$?
     fi

exit $RET
