An EXPERIMENTAL feature used with CURLOPT_ECH and --ech.
Closes #11922
#
# SPDX-License-Identifier: curl
#
+AAAA
ABI
accessor
ACK
AIX
al
Alessandro
+aliasMode
allocator
alnum
ALPN
CLAs
cleartext
CLI
+ClientHello
clientp
cliget
closesocket
cmake
CMake's
cmake's
+CNAME
+CNAMEs
CMakeLists
CNA
CodeQL
CURLcode
curldown
CURLE
+CURLECH
CURLH
curlimages
CURLINFO
Debian
DEBUGBUILD
decrypt
+decrypting
deepcode
DELE
DER
dns
dnsop
DoH
+DoT
doxygen
drftpd
dsa
ECC
ECDHE
ECH
+ecl
+ECHConfig
+ECHConfigList
ECONNREFUSED
eCOS
EFnet
GPG
GPL
GPLed
+GREASE
+GREASEing
Greear
groff
gsasl
Haxx
haxx
Heimdal
+HelloRetryRequest
HELO
HH
HMAC
hostname
hostnames
Housley
+HRR
Hruska
HSTS
hsts
Mavrogiannopoulos
Mbed
mbedTLS
+md
Meglio
memdebug
MesaLink
Micrium
MicroBlaze
MicroOS
+middlebox
mingw
MinGW
MINIX
PKGBUILD
PKI
pluggable
+pn
PolarSSL
Polhem
pollset
pycurl
pytest
Pytest
+qname
QNX
QoS
Qubes
Rockbox
roffit
RPG
+RR
+RRs
+RRtype
RSA
RTMP
rtmp
superset
svc
svcb
+SVCB
Svyatoslav
Swisscom
sws
libstandaloneengine.a
tests/string
tests/config
+tests/ech-log/
# HAVE_GNUTLS_SRP: `gnutls_srp_verifier` present in GnuTLS
# HAVE_SSL_CTX_SET_QUIC_METHOD: `SSL_CTX_set_quic_method` present in OpenSSL/wolfSSL
# HAVE_QUICHE_CONN_SET_QLOG_FD: `quiche_conn_set_qlog_fd` present in QUICHE
+# HAVE_ECH: ECH API checks for OpenSSL, boringssl or wolfSSL
#
# For each of the above variables, if the variable is DEFINED (either
# to ON or OFF), the symbol detection will be skipped. If the
endif()
endif()
+option(USE_HTTPSRR "Enable HTTPS RR support for ECH (experimental)" OFF)
+option(USE_ECH "Enable ECH support" OFF)
+if(USE_ECH)
+ if(USE_OPENSSL OR USE_WOLFSSL)
+ # Be sure that the OpenSSL/wolfSSL library actually supports ECH.
+ if(NOT DEFINED HAVE_ECH)
+ if(USE_OPENSSL AND HAVE_BORINGSSL)
+ openssl_check_symbol_exists(SSL_set1_ech_config_list "openssl/ssl.h" HAVE_ECH)
+ elseif(USE_OPENSSL)
+ openssl_check_symbol_exists(SSL_ech_set1_echconfig "openssl/ech.h" HAVE_ECH)
+ elseif(USE_WOLFSSL)
+ openssl_check_symbol_exists(wolfSSL_CTX_GenerateEchConfig "wolfssl/options.h;wolfssl/ssl.h" HAVE_ECH)
+ endif()
+ endif()
+ if(NOT HAVE_ECH)
+ message(FATAL_ERROR "ECH support missing in OpenSSL/BoringSSL/wolfSSL")
+ else()
+ message("ECH enabled.")
+ endif()
+ else()
+ message(FATAL_ERROR "ECH requires ECH-enablded OpenSSL, BoringSSL or wolfSSL")
+ endif()
+endif()
+
+
option(USE_NGHTTP2 "Use nghttp2 library" OFF)
if(USE_NGHTTP2)
find_package(NGHTTP2 REQUIRED)
_add_if("IPFS" NOT CURL_DISABLE_HTTP)
_add_if("IPNS" NOT CURL_DISABLE_HTTP)
_add_if("HTTPS" NOT CURL_DISABLE_HTTP AND SSL_ENABLED)
+ _add_if("ECH" HAVE_ECH)
+ _add_if("HTTPSRR" HAVE_ECH)
_add_if("FTP" NOT CURL_DISABLE_FTP)
_add_if("FTPS" NOT CURL_DISABLE_FTP AND SSL_ENABLED)
_add_if("FILE" NOT CURL_DISABLE_FILE)
CURL_CHECK_OPTION_SYMBOL_HIDING
CURL_CHECK_OPTION_ARES
CURL_CHECK_OPTION_RT
+CURL_CHECK_OPTION_HTTPSRR
CURL_CHECK_OPTION_ECH
XC_CHECK_PATH_SEPARATOR
AC_DEFINE(CURL_DISABLE_HSTS, 1, [disable alt-svc])
fi
+
+dnl *************************************************************
+dnl check whether HTTPSRR support if desired
+dnl
+if test "x$want_httpsrr" != "xno"; then
+ AC_MSG_RESULT([HTTPSRR support is available])
+ AC_DEFINE(USE_HTTPSRR, 1, [enable HTTPS RR support])
+ experimental="$experimental HTTPSRR"
+fi
+
dnl *************************************************************
dnl check whether ECH support, if desired, is actually available
dnl
ECH_ENABLED=0
ECH_SUPPORT=''
- dnl OpenSSL with a chosen ECH function should be enough
- dnl so more exhaustive checking seems unnecessary for now
+ dnl check for OpenSSL
if test "x$OPENSSL_ENABLED" = "x1"; then
- AC_CHECK_FUNCS(SSL_get_ech_status,
- ECH_SUPPORT="ECH support available (OpenSSL with SSL_get_ech_status)"
+ AC_CHECK_FUNCS(SSL_ech_set1_echconfig,
+ ECH_SUPPORT="ECH support available via OpenSSL with SSL_ech_set1_echconfig"
+ ECH_ENABLED=1)
+ fi
+ dnl check for boringssl equivalent
+ if test "x$OPENSSL_ENABLED" = "x1"; then
+ AC_CHECK_FUNCS(SSL_set1_ech_config_list,
+ ECH_SUPPORT="ECH support available via boringssl with SSL_set1_ech_config_list"
+ ECH_ENABLED=1)
+ fi
+ if test "x$WOLFSSL_ENABLED" = "x1"; then
+ AC_CHECK_FUNCS(wolfSSL_CTX_GenerateEchConfig,
+ ECH_SUPPORT="ECH support available via WolfSSL with wolfSSL_CTX_GenerateEchConfig"
ECH_ENABLED=1)
-
- dnl add 'elif' chain here for additional implementations
fi
dnl now deal with whatever we found
if test "x$ECH_ENABLED" = "x1"; then
+ dnl force pre-requisites for ECH
+ AC_DEFINE(USE_HTTPSRR, 1, [force HTTPS RR support for ECH])
AC_DEFINE(USE_ECH, 1, [if ECH support is available])
AC_MSG_RESULT($ECH_SUPPORT)
experimental="$experimental ECH"
AC_MSG_RESULT([no])
fi
-if test "x$ECH_ENABLED" = "x1"; then
- SUPPORT_FEATURES="$SUPPORT_FEATURES ECH"
-fi
-
if test ${ac_cv_sizeof_curl_off_t} -gt 4; then
if test ${ac_cv_sizeof_off_t} -gt 4 -o \
"$curl_win32_file_api" = "win32_large_files"; then
--- /dev/null
+<!--
+Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+
+SPDX-License-Identifier: curl
+-->
+
+# Building curl with HTTPS-RR and ECH support
+
+We've added support for ECH to in this curl build. That can use HTTPS RRs
+published in the DNS, if curl is using DoH, or else can accept the relevant
+ECHConfigList values from the command line. That works with OpenSSL,
+WolfSSL or boringssl as the TLS provider, depending on how you build curl.
+
+This feature is EXPERIMENTAL. DO NOT USE IN PRODUCTION.
+
+This should however provide enough of a proof-of-concept to prompt an informed
+discussion about a good path forward for ECH support in curl, when using
+OpenSSL, or other TLS libraries, as those add ECH support.
+
+## OpenSSL Build
+
+To build our ECH-enabled OpenSSL fork:
+
+```bash
+ cd $HOME/code
+ git clone https://github.com/defo-project/openssl
+ cd openssl
+ ./config --libdir=lib --prefix=$HOME/code/openssl-local-inst
+ ...stuff...
+ make -j8
+ ...stuff (maybe go for coffee)...
+ make install_sw
+ ...a little bit of stuff...
+```
+
+To build curl ECH-enabled, making use of the above:
+
+```bash
+ cd $HOME/code
+ git clone https://github.com/curl/curl
+ cd curl
+ autoreconf -fi
+ LDFLAGS="-Wl,-rpath,$HOME/code/openss-local-inst/lib/" ./configure --with-ssl=$HOME/code/openssl-local-inst --enable-ech --enable-httpsrr
+ ...lots of output...
+ WARNING: ech ECH HTTPSRR enabled but marked EXPERIMENTAL...
+ make
+ ...lots more output...
+```
+
+If you do not get that WARNING at the end of the ``configure`` command, then ECH
+is not enabled, so go back some steps and re-do whatever needs re-doing:-) If you
+want to debug curl then you should add ``--enable-debug`` to the ``configure``
+command.
+
+With the above build, I still need to set ``LD_LIBRARY_PATH`` to run the
+version of curl built against OpenSSL in my development environment (Ubuntu
+23.10).
+
+## Using ECH and DoH
+
+Curl supports using DoH for A/AAAA lookups so it was relatively easy to add
+retrieval of HTTPS RRs in that situation. To use ECH and DoH together:
+
+```bash
+ cd $HOME/code/curl
+ LD_LIBRARY_PATH=$HOME/code/openssl ./src/curl --ech true --doh-url https://one.one.one.one/dns-query https://defo.ie/ech-check.php
+ ...
+ SSL_ECH_STATUS: success <img src="greentick-small.png" alt="good" /> <br/>
+ ...
+```
+
+The output snippet above is within the HTML for the webpage, when things work.
+
+The above works for these test sites:
+
+```bash
+ https://defo.ie/ech-check.php
+ https://draft-13.esni.defo.ie:8413/stats
+ https://draft-13.esni.defo.ie:8414/stats
+ https://crypto.cloudflare.com/cdn-cgi/trace
+ https://tls-ech.dev
+```
+
+The list above has 4 different server technologies, implemented by 3 different
+parties, and includes a case (the port 8414 server) where HelloRetryRequest
+(HRR) is forced.
+
+We currently support the following new curl command line arguments/options:
+
+- ``--ech <config>`` - the ``config`` value can be one of:
+ - ``false`` says to not attempt ECH
+ - ``true`` says to attempt ECH, if possible
+ - ``grease`` if attempting ECH is not possible, then send a GREASE ECH extension
+ - ``hard`` hard-fail the connection if ECH cannot be attempted
+ - ``ecl:<b64value>`` a base64 encoded ECHConfigList, rather than one accessed from the DNS
+ - ``pn:<name>`` over-ride the ``public_name`` from an ECHConfigList
+
+Note that in the above "attempt ECH" means the client emitting a TLS
+ClientHello with a "real" ECH extension, but that does not mean that the
+relevant server can succeed in decrypting, as things can fail for other
+reasons.
+
+## Supplying an ECHConfigList on the command line
+
+To supply the ECHConfigList on the command line, you might need a bit of
+cut-and-paste, e.g.:
+
+```bash
+ dig +short https defo.ie
+ 1 . ipv4hint=213.108.108.101 ech=AED+DQA8PAAgACD8WhlS7VwEt5bf3lekhHvXrQBGDrZh03n/LsNtAodbUAAEAAEAAQANY292ZXIuZGVmby5pZQAA ipv6hint=2a00:c6c0:0:116:5::10
+```
+
+Then paste the base64 encoded ECHConfigList onto the curl command line:
+
+```bash
+ LD_LIBRARY_PATH=$HOME/code/openssl ./src/curl --ech ecl:AED+DQA8PAAgACD8WhlS7VwEt5bf3lekhHvXrQBGDrZh03n/LsNtAodbUAAEAAEAAQANY292ZXIuZGVmby5pZQAA https://defo.ie/ech-check.php
+ ...
+ SSL_ECH_STATUS: success <img src="greentick-small.png" alt="good" /> <br/>
+ ...
+```
+
+The output snippet above is within the HTML for the webpage.
+
+If you paste in the wrong ECHConfigList (it changes hourly for ``defo.ie``) you
+should get an error like this:
+
+```bash
+ LD_LIBRARY_PATH=$HOME/code/openssl ./src/curl -vvv --ech ecl:AED+DQA8yAAgACDRMQo+qYNsNRNj+vfuQfFIkrrUFmM4vogucxKj/4nzYgAEAAEAAQANY292ZXIuZGVmby5pZQAA https://defo.ie/ech-check.php
+ ...
+ * OpenSSL/3.3.0: error:0A00054B:SSL routines::ech required
+ ...
+```
+
+There is a reason to want this command line option - for use before publishing
+an ECHConfigList in the DNS as per the Internet-draft [A well-known URI for
+publishing ECHConfigList values](https://datatracker.ietf.org/doc/draft-ietf-tls-wkech/).
+
+If you do use a wrong ECHConfigList value, then the server might return a
+good value, via the ``retry_configs`` mechanism. You can see that value in
+the verbose output, e.g.:
+
+```bash
+ LD_LIBRARY_PATH=$HOME/code/openssl ./src/curl -vvv --ech ecl:AED+DQA8yAAgACDRMQo+qYNsNRNj+vfuQfFIkrrUFmM4vogucxKj/4nzYgAEAAEAAQANY292ZXIuZGVmby5pZQAA https://defo.ie/ech-check.php
+ ...
+* ECH: retry_configs AQD+DQA8DAAgACBvYqJy+Hgk33wh/ZLBzKSPgwxeop7gvojQzfASq7zeZQAEAAEAAQANY292ZXIuZGVmby5pZQAA/g0APEMAIAAgXkT5r4cYs8z19q5rdittyIX8gfQ3ENW4wj1fVoiJZBoABAABAAEADWNvdmVyLmRlZm8uaWUAAP4NADw2ACAAINXSE9EdXzEQIJZA7vpwCIQsWqsFohZARXChgPsnfI1kAAQAAQABAA1jb3Zlci5kZWZvLmllAAD+DQA8cQAgACASeiD5F+UoSnVoHvA2l1EifUVMFtbVZ76xwDqmMPraHQAEAAEAAQANY292ZXIuZGVmby5pZQAA
+* ECH: retry_configs for defo.ie from cover.defo.ie, 319
+ ...
+```
+
+At that point, you could copy the base64 encoded value above and try again.
+For now, this only works for the OpenSSL and boringssl builds.
+
+## Default settings
+
+Curl has various ways to configure default settings, e.g. in ``$HOME/.curlrc``,
+so one can set the DoH URL and enable ECH that way:
+
+```bash
+ cat ~/.curlrc
+ doh-url=https://one.one.one.one/dns-query
+ silent
+ ech=true
+```
+
+Note that when you use the system's curl command (rather than our ECH-enabled
+build), it is liable to warn that ``ech`` is an unknown option. If that is an
+issue (e.g. if some script re-directs stdout and stderr somewhere) then adding
+the ``silent`` line above seems to be a good enough fix. (Though of
+course, yet another script could depend on non-silent behavior, so you may have
+to figure out what you prefer yourself.) That seems to have changed with the
+latest build, previously ``silent=TRUE`` was what I used in ``~/.curlrc`` but
+now that seems to cause a problem, so that the following line(s) are ignored.
+
+If you want to always use our OpenSSL build you can set ``LD_LIBRARY_PATH``
+in the environment:
+
+```bash
+ export LD_LIBRARY_PATH=$HOME/code/openssl
+```
+
+When you do the above, there can be a mismatch between OpenSSL versions
+for applications that check that. A ``git push`` for example fails so you
+should unset ``LD_LIBRARY_PATH`` before doing that or use a different shell.
+
+```bash
+ git push
+ OpenSSL version mismatch. Built against 30000080, you have 30200000
+ ...
+```
+
+With all that setup as above the command line gets simpler:
+
+```bash
+ ./src/curl https://defo.ie/ech-check.php
+ ...
+ SSL_ECH_STATUS: success <img src="greentick-small.png" alt="good" /> <br/>
+ ...
+```
+
+The ``--ech true`` option is opportunistic, so tries to do ECH but does not fail if
+the client for example cannot find any ECHConfig values. The ``--ech hard``
+option hard-fails if there is no ECHConfig found in DNS, so for now, that is not
+a good option to set as a default. Once ECH has really been attempted by
+the client, if decryption on the server side fails, then curl fails.
+
+## Code changes for ECH support when using DoH
+
+Code changes are ``#ifdef`` protected via ``USE_ECH`` or ``USE_HTTPSRR``:
+
+- ``USE_HTTPSRR`` is used for HTTPS RR retrieval code that could be generically
+ used should non-ECH uses for HTTPS RRs be identified, e.g. use of ALPN values
+or IP address hints.
+
+- ``USE_ECH`` protects ECH specific code.
+
+There are various obvious code blocks for handling the new command line
+arguments which aren't described here, but should be fairly clear.
+
+As shown in the ``configure`` usage above, there are ``configure.ac`` changes
+that allow separately dis/enabling ``USE_HTTPSRR`` and ``USE_ECH``. If ``USE_ECH``
+is enabled, then ``USE_HTTPSRR`` is forced. In both cases ``USE_DOH``
+is required. (There may be some configuration conflicts available for the
+determined:-)
+
+The main functional change, as you would expect, is in ``lib/vtls/openssl.c``
+where an ECHConfig, if available from command line or DNS cache, is fed into
+the OpenSSL library via the new APIs implemented in our OpenSSL fork for that
+purpose. This code also implements the opportunistic (``--ech true``) or hard-fail
+(``--ech hard``) logic.
+
+Other than that, the main additions are in ``lib/doh.c``
+where we re-use ``dohprobe()`` to retrieve an HTTPS RR value for the target
+domain. If such a value is found, that is stored using a new ``store_https()``
+function in a new field in the ``dohentry`` structure.
+
+The qname for the DoH query is modified if the port number is not 443, as
+defined in the SVCB specification.
+
+When the DoH process has worked, ``Curl_doh_is_resolved()`` now also returns
+the relevant HTTPS RR value data in the ``Curl_dns_entry`` structure.
+That is later accessed when the TLS session is being established, if ECH is
+enabled (from ``lib/vtls/openssl.c`` as described above).
+
+## Limitations
+
+Things that need fixing, but that can probably be ignored for the
+moment:
+
+- We could easily add code to make use of an ``alpn=`` value found in an HTTPS
+ RR, passing that on to OpenSSL for use as the "inner" ALPN value, but have
+yet to do that.
+
+Current limitations (more interesting than the above):
+
+- Only the first HTTPS RR value retrieved is actually processed as described
+ above, that could be extended in future, though picking the "right" HTTPS RR
+could be non-trivial if multiple RRs are published - matching IP address hints
+versus A/AAAA values might be a good basis for that. Last I checked though,
+browsers supporting ECH did not handle multiple HTTPS RRs well, though that
+needs re-checking as it has been a while.
+
+- It is unclear how one should handle any IP address hints found in an HTTPS RR.
+ It may be that a bit of consideration of how "multi-CDN" deployments might
+emerge would provide good answers there, but for now, it is not clear how best
+curl might handle those values when present in the DNS.
+
+- The SVCB/HTTPS RR specification supports a new "CNAME at apex" indirection
+ ("aliasMode") - the current code takes no account of that at all. One could
+envisage implementing the equivalent of following CNAMEs in such cases, but
+it is not clear if that'd be a good plan. (As of now, chrome browsers do not seem
+to have any support for that "aliasMode" and we've not checked Firefox for that
+recently.)
+
+- We have not investigated what related changes or additions might be needed
+ for applications using libcurl, as opposed to use of curl as a command line
+tool.
+
+- We have not yet implemented tests as part of the usual curl test harness as
+doing so would seem to require re-implementing an ECH-enabled server as part
+of the curl test harness. For now, we have a ``./tests/ech_test.sh`` script
+that attempts ECH with various test servers and with many combinations of the
+allowed command line options. While that is a useful test and has find issues,
+it is not comprehensive and we're not (as yet) sure what would be the right
+level of coverage. When running that script you should not have a
+``$HOME/.curlrc`` file that affects ECH or some of the negative tests could
+produce spurious failures.
+
+## Building with cmake
+
+To build with cmake, assuming our ECH-enabled OpenSSL is as before:
+
+```bash
+ cd $HOME/code
+ git clone https://github.com/curl/curl
+ cd curl
+ mkdir build
+ cd build
+ cmake -DOPENSSL_ROOT_DIR=$HOME/code/openssl -DUSE_ECH=1 -DUSE_HTTPSRR=1 ..
+ ...
+ make
+ ...
+ [100%] Built target curl
+```
+
+The binary produced by the cmake build does not need any ECH-specific
+``LD_LIBRARY_PATH`` setting.
+
+## boringssl build
+
+BoringSSL is also supported by curl and also supports ECH, so to build
+with that, instead of our ECH-enabled OpenSSL:
+
+```bash
+ cd $HOME/code
+ git clone https://boringssl.googlesource.com/boringssl
+ cd boringssl
+ cmake -DCMAKE_INSTALL_PREFIX:PATH=$HOME/code/boringssl/inst -DBUILD_SHARED_LIBS=1
+ make
+ ...
+ make install
+```
+
+Then:
+
+```bash
+ cd $HOME/code
+ git clone https://github.com/curl/curl
+ cd curl
+ autoreconf -fi
+ LDFLAGS="-Wl,-rpath,$HOME/code/boringssl/inst/lib" ./configure --with-ssl=$HOME/code/boringssl/inst --enable-ech --enable-httpsrr
+ ...lots of output...
+ WARNING: ech ECH HTTPSRR enabled but marked EXPERIMENTAL. Use with caution!
+ make
+```
+
+The boringssl APIs are fairly similar to those in our ECH-enabled OpenSSL
+fork, so code changes are also in ``lib/vtls/openssl.c``, protected
+via ``#ifdef OPENSSL_IS_BORINGSSL`` and are mostly obvious API variations.
+
+The boringssl APIs however do not support the ``--ech pn:`` command line
+variant as of now.
+
+## WolfSSL build
+
+WolfSSL also supports ECH and can be used by curl, so here's how:
+
+```bash
+ cd $HOME/code
+ git clone https://github.com/wolfSSL/wolfssl
+ cd wolfssl
+ ./autogen.sh
+ ./configure --prefix=$HOME/code/wolfssl/inst --enable-ech --enable-debug --enable-opensslextra
+ make
+ make install
+```
+
+The install prefix (``inst``) in the above causes WolfSSL to be installed there
+and we seem to need that for the curl configure command to work out. The
+``--enable-opensslextra`` turns out (after much faffing about;-) to be
+important or else we get build problems with curl below.
+
+```bash
+ cd $HOME/code
+ git clone https://github.com/curl/curl
+ cd curl
+ autoreconf -fi
+ ./configure --with-wolfssl=$HOME/code/wolfssl/inst --enable-ech --enable-httpsrr
+ make
+```
+
+There are some known issues with the ECH implementation in WolfSSL:
+
+- The main issue is that the client currently handles HelloRetryRequest
+ incorrectly. [HRR issue](https://github.com/wolfSSL/wolfssl/issues/6802).)
+ The HRR issue means that the client does not work for
+ [this ECH test web site](https://tls-ech.dev) and any other similarly configured
+ sites.
+- There is also an issue related to so-called middlebox compatibility mode.
+ [middlebox compatibility issue](https://github.com/wolfSSL/wolfssl/issues/6774)
+
+### Code changes to support WolfSSL
+
+There are what seem like oddball differences:
+
+- The DoH URL in``$HOME/.curlrc`` can use "1.1.1.1" for OpenSSL but has to be
+ "one.one.one.one" for WolfSSL. The latter works for both, so OK, we'll change
+ to that.
+- There seems to be some difference in CA databases too - the WolfSSL version
+ does not like ``defo.ie``, whereas the system and OpenSSL ones do. We can ignore
+ that for our purposes via ``--insecure``/``-k`` but would need to fix for a
+ real setup. (Browsers do like those certificates though.)
+
+Then there are some functional code changes:
+
+- tweak to ``configure.ac`` to check if WolfSSL has ECH or not
+- added code to ``lib/vtls/wolfssl.c`` mirroring what's done in the
+ OpenSSL equivalent above.
+- WolfSSL does not support ``--ech false`` or the ``--ech pn:`` command line
+ argument.
+
+The lack of support for ``--ech false`` is because wolfSSL has decided to
+always at least GREASE if built to support ECH. In other words, GREASE is
+a compile time choice for wolfSSL, but a runtime choice for OpenSSL or
+boringssl. (Both are reasonable.)
+
+## Additional notes
+
+### Supporting ECH without DoH
+
+All of the above only applies if DoH is being used. There should be a use-case
+for ECH when DoH is not used by curl - if a system stub resolver supports DoT
+or DoH, then, considering only ECH and the network threat model, it would make
+sense for curl to support ECH without curl itself using DoH. The author for
+example uses a combination of stubby+unbound as the system resolver listening
+on localhost:53, so would fit this use-case. That said, it is unclear if
+this is a niche that is worth trying to address. (The author is just as happy to
+let curl use DoH to talk to the same public recursive that stubby might use:-)
+
+Assuming for the moment this is a use-case we'd like to support, then
+if DoH is not being used by curl, it is not clear at this time how to provide
+support for ECH. One option would seem to be to extend the ``c-ares`` library
+to support HTTPS RRs, but in that case it is not now clear whether such changes
+would be attractive to the ``c-ares`` maintainers, nor whether the "tag=value"
+extensibility inherent in the HTTPS/SVCB specification is a good match for the
+``c-ares`` approach of defining structures specific to decoded answers for each
+supported RRtype. We're also not sure how many downstream curl deployments
+actually make use of the ``c-ares`` library, which would affect the utility of
+such changes. Another option might be to consider using some other generic DNS
+library that does support HTTPS RRs, but it is unclear if such a library could
+or would be used by all or almost all curl builds and downstream releases of
+curl.
+
+Our current conclusion is that doing the above is likely best left until we
+have some experience with the "using DoH" approach, so we're going to punt on
+this for now.
+
+### Debugging
+
+Just a note to self as remembering this is a nuisance:
+
+```bash
+LD_LIBRARY_PATH=$HOME/code/openssl:./lib/.libs gdb ./src/.libs/curl
+```
+
+### Localhost testing
+
+It can be useful to be able to run against a localhost OpenSSL ``s_server``
+for testing. We have published instructions for such
+[localhost tests](https://github.com/defo-project/ech-dev-utils/blob/main/howtos/localhost-tests.md)
+in another repository. Once you have that set up, you can start a server
+and then run curl against that:
+
+```bash
+ cd $HOME/code/ech-dev-utils
+ ./scripts/echsvr.sh -d
+ ...
+```
+
+The ``echsvr.sh`` script supports many ECH-related options. Use ``echsvr.sh -h``
+for details.
+
+In another window:
+
+```bash
+ cd $HOME/code/curl/
+ ./src/curl -vvv --insecure --connect-to foo.example.com:8443:localhost:8443 --ech ecl:AD7+DQA6uwAgACBix2B78sX+EQhEbxMspDOc8Z3xVS5aQpYP0Cxpc2AWPAAEAAEAAQALZXhhbXBsZS5jb20AAA==
+```
+
+### Automated use of ``retry_configs`` not supported so far...
+
+As of now we have not added support for using ``retry_config`` handling in the
+application - for a command line tool, one can just use ``dig`` (or ``kdig``)
+to get the HTTPS RR and pass the ECHConfigList from that on the command line,
+if needed, or one can access the value from command line output in verbose more
+and then re-use that in another invocation.
+
+Both our OpenSSL fork and boringssl have APIs for both controlling GREASE and
+accessing and logging ``retry_configs``, it seems WolfSSL has neither.
+
- HTTP/3 support (using the quiche or msh3 backends)
- The rustls backend
- WebSocket
+ - Use of the HTTPS resource record and Encrypted Client Hello (ECH) when using DoH
doh-insecure.md \
doh-url.md \
dump-header.md \
+ ech.md \
egd-file.md \
engine.md \
etag-compare.md \
--- /dev/null
+---
+c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+SPDX-License-Identifier: curl
+Long: ech
+Arg: <config>
+Help: Configure Encrypted Client Hello (ECH) for use with the TLS session
+Added: 8.8.0
+Category: tls ECH
+Protocols: HTTPS
+Multi: single
+See-also:
+ - doh-url
+Example:
+ - --ech true $URL
+---
+
+# `--ech`
+
+Specifies how to do ECH (Encrypted Client Hello).
+
+The values allowed for \<config\> can be:
+
+## "false"
+Do not attempt ECH
+
+## "grease"
+
+Send a GREASE ECH extension
+
+## "true"
+
+Attempt ECH if possible, but do not fail if ECH is not attempted.
+(The connection fails if ECH is attempted but fails.)
+
+## "hard"
+
+Attempt ECH and fail if that is not possible.
+ECH only works with TLS 1.3 and also requires using
+DoH or providing an ECHConfigList on the command line.
+
+## "ecl:<b64val>"
+
+A base64 encoded ECHConfigList that is used for ECH.
+
+## "pn:<name>"
+
+A name to use to over-ride the `public_name` field of an ECHConfigList
+(only available with OpenSSL TLS support)
+
+## Errors
+
+Most errors cause error
+*CURLE_ECH_REQUIRED* (101).
+
}
~~~
+# ENCRYPTED CLIENT HELLO OPTIONS
+
+## CURLOPT_ECH
+
+Set the configuration for ECH. See CURLOPT_ECH(3)
+
# AVAILABILITY
Always
A value or data field grew larger than allowed.
+## CURLE_ECH_REQUIRED (101)"
+
+ECH was attempted but failed.
+
# CURLMcode
This is the generic return code used by functions in the libcurl multi
--- /dev/null
+---
+c: Copyright (C) Daniel Stenberg, <daniel.se>, et al.
+SPDX-License-Identifier: curl
+Title: CURLOPT_ECH
+Section: 3
+Source: libcurl
+See-also:
+ - (3)
+Protocol:
+ - TLS
+TLS-backend:
+ - OpenSSL
+ - wolfSSL
+---
+
+# NAME
+
+CURLOPT_ECH - configuration for Encrypted Client Hello
+
+# SYNOPSIS
+
+~~~c
+#include <curl/curl.h>
+
+CURLcode curl_easy_setopt(CURL *handle, CURLOPT_ECH, char *config);
+~~~
+
+# DESCRIPTION
+
+ECH is only compatible with TLSv1.3.
+
+This experimental feature requires a special build of OpenSSL, as ECH is not
+yet supported in OpenSSL releases. In contrast ECH is supported by the latest
+BoringSSL and wolfSSL releases. See [ECH.md](../../ECH.md) for details of how
+to build such an OpenSSL library.
+
+There is also a known issue with using wolfSSL which does not support ECH
+when the HelloRetryRequest mechanism is used.
+
+Pass a string that specifies configuration details for ECH.
+In all cases, if ECH is attempted, it may fail for various reasons.
+The keywords supported are:
+
+## false
+Turns off ECH.
+## grease
+Instructs client to emit a GREASE ECH extension.
+(The connection fails if ECH is attempted but fails.)
+## true
+Instructs client to attempt ECH, if possible, but to not fail if attempting ECH is not possible.
+## hard
+Instructs client to attempt ECH and fail if if attempting ECH is not possible.
+## ecl:\<base64-value\>
+If the string starts with "ecl:" then the remainder of the string should be a base64-encoded
+ECHConfigList that is used for ECH rather than attempting to download such a value from
+the DNS.
+## pn:\<name\>
+If the string starts with "pn:" then the remainder of the string should be a DNS/hostname
+that is used to over-ride the public_name field of the ECHConfigList that is used
+for ECH.
+
+# DEFAULT
+
+NULL, meaning ECH is disabled.
+
+# EXAMPLE
+
+~~~c
+CURL *curl = curl_easy_init();
+
+const char *config ="ecl:AED+DQA87wAgACB/RuzUCsW3uBbSFI7mzD63TUXpI8sGDTnFTbFCDpa+CAAEAAEAAQANY292ZXIuZGVmby5pZQAA";
+if(curl) {
+ curl_easy_setopt(curl, CURLOPT_ECH, config);
+ curl_easy_perform(curl);
+}
+~~~
+# AVAILABILITY
+
+Added in 8.8.0
+
+# RETURN VALUE
+
+Returns CURLE_OK on success or CURLE_OUT_OF_MEMORY if there was insufficient heap space.
CURLOPT_DOH_SSL_VERIFYPEER.3 \
CURLOPT_DOH_SSL_VERIFYSTATUS.3 \
CURLOPT_DOH_URL.3 \
+ CURLOPT_ECH.3 \
CURLOPT_EGDSOCKET.3 \
CURLOPT_ERRORBUFFER.3 \
CURLOPT_EXPECT_100_TIMEOUT_MS.3 \
CURLE_USE_SSL_FAILED 7.17.0
CURLE_WEIRD_SERVER_REPLY 7.51.0
CURLE_WRITE_ERROR 7.1
+CURLE_ECH_REQUIRED 8.8.0
CURLFILETYPE_DEVICE_BLOCK 7.21.0
CURLFILETYPE_DEVICE_CHAR 7.21.0
CURLFILETYPE_DIRECTORY 7.21.0
CURLOPT_DOH_SSL_VERIFYPEER 7.76.0
CURLOPT_DOH_SSL_VERIFYSTATUS 7.76.0
CURLOPT_DOH_URL 7.62.0
+CURLOPT_ECH 8.8.0
CURLOPT_EGDSOCKET 7.7 7.84.0
CURLOPT_ENCODING 7.10 7.21.6
CURLOPT_ERRORBUFFER 7.1
--doh-insecure 7.76.0
--doh-url 7.62.0
--dump-header (-D) 5.7
+--ech 8.8.0
--egd-file 7.7
--engine 7.9.3
--etag-compare 7.68.0
CURLE_SSL_CLIENTCERT, /* 98 - client-side certificate required */
CURLE_UNRECOVERABLE_POLL, /* 99 - poll/select returned fatal error */
CURLE_TOO_LARGE, /* 100 - a value/data met its maximum */
+ CURLE_ECH_REQUIRED, /* 101 - ECH tried but failed */
CURL_LAST /* never use! */
} CURLcode;
/* millisecond version */
CURLOPT(CURLOPT_SERVER_RESPONSE_TIMEOUT_MS, CURLOPTTYPE_LONG, 324),
+ /* set ECH configuration */
+ CURLOPT(CURLOPT_ECH, CURLOPTTYPE_STRINGPOINT, 325),
+
CURLOPT_LASTENTRY /* the last unused */
} CURLoption;
#define CURL_VERSION_GSASL (1<<29) /* libgsasl is supported */
#define CURL_VERSION_THREADSAFE (1<<30) /* libcurl API is thread-safe */
- /*
+/*
* NAME curl_version_info()
*
* DESCRIPTION
(option) == CURLOPT_DNS_LOCAL_IP6 || \
(option) == CURLOPT_DNS_SERVERS || \
(option) == CURLOPT_DOH_URL || \
+ (option) == CURLOPT_ECH || \
(option) == CURLOPT_EGDSOCKET || \
(option) == CURLOPT_FTP_ACCOUNT || \
(option) == CURLOPT_FTP_ALTERNATIVE_TO_USER || \
/* Define to 1 to enable TLS-SRP support. */
#cmakedefine USE_TLS_SRP 1
+
+/* Define to 1 to query for HTTPSRR when using DoH */
+#cmakedefine USE_HTTPSRR 1
+
+/* if ECH support is available */
+#cmakedefine USE_ECH 1
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"
+#include "escape.h"
#define DNS_CLASS_IN 0x01
+/* local_print_buf truncates if the hex string will be more than this */
+#define LOCAL_PB_HEXMAX 400
+
#ifndef CURL_DISABLE_VERBOSE_STRINGS
static const char * const errors[]={
"",
return realsize;
}
+#if defined(USE_HTTPSRR) && defined(CURLDEBUG)
+static void local_print_buf(struct Curl_easy *data,
+ const char *prefix,
+ unsigned char *buf, size_t len)
+{
+ unsigned char hexstr[LOCAL_PB_HEXMAX];
+ size_t hlen = LOCAL_PB_HEXMAX;
+ bool truncated = false;
+
+ if(len > (LOCAL_PB_HEXMAX / 2))
+ truncated = true;
+ Curl_hexencode(buf, len, hexstr, hlen);
+ if(!truncated)
+ infof(data, "%s: len=%d, val=%s", prefix, (int)len, hexstr);
+ else
+ infof(data, "%s: len=%d (truncated)val=%s", prefix, (int)len, hexstr);
+ return;
+}
+#endif
+
/* called from multi.c when this DoH transfer is complete */
static int doh_done(struct Curl_easy *doh, CURLcode result)
{
int slot;
struct dohdata *dohp;
struct connectdata *conn = data->conn;
+#ifdef USE_HTTPSRR
+ /* for now, this is only used when ECH is enabled */
+# ifdef USE_ECH
+ char *qname = NULL;
+# endif
+#endif
*waitp = FALSE;
(void)hostname;
(void)port;
goto error;
dohp->pending++;
}
+#endif
+
+#ifdef USE_HTTPSRR
+ /*
+ * TODO: Figure out the conditions under which we want to make
+ * a request for an HTTPS RR when we are not doing ECH. For now,
+ * making this request breaks a bunch of DoH tests, e.g. test2100,
+ * where the addiitonal request doesn't match the pre-cooked data
+ * files, so there's a bit of work attached to making the request
+ * in a non-ECH use-case. For the present, we'll only make the
+ * request when ECH is enabled in the build and is being used for
+ * the curl operation.
+ */
+# ifdef USE_ECH
+ if(data->set.tls_ech & CURLECH_ENABLE
+ || data->set.tls_ech & CURLECH_HARD) {
+ if(port == 443)
+ qname = strdup(hostname);
+ else
+ qname = aprintf("_%d._https.%s", port, hostname);
+ if(!qname)
+ goto error;
+ result = dohprobe(data, &dohp->probe[DOH_PROBE_SLOT_HTTPS],
+ DNS_TYPE_HTTPS, qname, data->set.str[STRING_DOH],
+ data->multi, dohp->headers);
+ free(qname);
+ if(result)
+ goto error;
+ dohp->pending++;
+ }
+# endif
#endif
*waitp = TRUE; /* this never returns synchronously */
return NULL;
return DOH_OK;
}
+#ifdef USE_HTTPSRR
+static DOHcode store_https(const unsigned char *doh,
+ int index,
+ struct dohentry *d,
+ uint16_t len)
+{
+ /* silently ignore RRs over the limit */
+ if(d->numhttps_rrs < DOH_MAX_HTTPS) {
+ struct dohhttps_rr *h = &d->https_rrs[d->numhttps_rrs];
+ h->val = Curl_memdup(&doh[index], len);
+ if(!h->val)
+ return DOH_OUT_OF_MEM;
+ h->len = len;
+ d->numhttps_rrs++;
+ }
+ return DOH_OK;
+}
+#endif
+
static DOHcode store_cname(const unsigned char *doh,
size_t dohlen,
unsigned int index,
/* RDATA
- A (TYPE 1): 4 bytes
- AAAA (TYPE 28): 16 bytes
- - NS (TYPE 2): N bytes */
+ - NS (TYPE 2): N bytes
+ - HTTPS (TYPE 65): N bytes */
DOHcode rc;
switch(type) {
if(rc)
return rc;
break;
+#ifdef USE_HTTPSRR
+ case DNS_TYPE_HTTPS:
+ rc = store_https(doh, index, d, rdlength);
+ if(rc)
+ return rc;
+ break;
+#endif
case DNS_TYPE_CNAME:
rc = store_cname(doh, dohlen, index, d);
if(rc)
if(index != dohlen)
return DOH_DNS_MALFORMAT; /* something is wrong */
+#ifdef USE_HTTTPS
+ if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr && !d->numhttps_rrs)
+#else
if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr)
+#endif
/* nothing stored! */
return DOH_NO_CONTENT;
infof(data, "%s", buffer);
}
}
+#ifdef USE_HTTPSRR
+ for(i = 0; i < d->numhttps_rrs; i++) {
+# ifdef CURLDEBUG
+ local_print_buf(data, "DoH HTTPS",
+ d->https_rrs[i].val, d->https_rrs[i].len);
+# else
+ infof(data, "DoH HTTPS RR: length %d", d->https_rrs[i].len);
+# endif
+ }
+#endif
for(i = 0; i < d->numcname; i++) {
infof(data, "CNAME: %s", Curl_dyn_ptr(&d->cname[i]));
}
#ifndef CURL_DISABLE_VERBOSE_STRINGS
static const char *type2name(DNStype dnstype)
{
- return (dnstype == DNS_TYPE_A)?"A":"AAAA";
+ switch(dnstype) {
+ case DNS_TYPE_A:
+ return "A";
+ case DNS_TYPE_AAAA:
+ return "AAAA";
+#ifdef USE_HTTPSRR
+ case DNS_TYPE_HTTPS:
+ return "HTTPS";
+#endif
+ default:
+ return "unknown";
+ }
}
#endif
for(i = 0; i < d->numcname; i++) {
Curl_dyn_free(&d->cname[i]);
}
+#ifdef USE_HTTPSRR
+ for(i = 0; i < d->numhttps_rrs; i++)
+ free(d->https_rrs[i].val);
+#endif
}
+#ifdef USE_HTTPSRR
+
+/*
+ * @brief decode the DNS name in a binary RRData
+ * @param buf points to the buffer (in/out)
+ * @param remaining points to the remaining buffer length (in/out)
+ * @param dnsname returns the string form name on success
+ * @return is 1 for success, error otherwise
+ *
+ * The encoding here is defined in
+ * https://tools.ietf.org/html/rfc1035#section-3.1
+ *
+ * The input buffer pointer will be modified so it points to
+ * just after the end of the DNS name encoding on output. (And
+ * that's why it's an "unsigned char **" :-)
+ */
+static CURLcode local_decode_rdata_name(unsigned char **buf, size_t *remaining,
+ char **dnsname)
+{
+ unsigned char *cp = NULL;
+ int rem = 0;
+ char *thename = NULL, *tp = NULL;
+ unsigned char clen = 0; /* chunk len */
+
+ if(!buf || !remaining || !dnsname)
+ return CURLE_OUT_OF_MEMORY;
+ rem = (int)*remaining;
+ thename = calloc(1, CURL_MAXLEN_host_name);
+ if(!thename)
+ return CURLE_OUT_OF_MEMORY;
+ cp = *buf;
+ tp = thename;
+ clen = *cp++;
+ if(clen == 0) {
+ /* special case - return "." as name */
+ thename[0] = '.';
+ thename[1] = 0x00;
+ }
+ while(clen) {
+ if(clen >= rem) {
+ free(thename);
+ return CURLE_OUT_OF_MEMORY;
+ }
+ if(((tp - thename) + clen) > CURL_MAXLEN_host_name) {
+ free(thename);
+ return CURLE_OUT_OF_MEMORY;
+ }
+ memcpy(tp, cp, clen);
+ tp += clen;
+ *tp++ = '.';
+ cp += clen;
+ rem -= (clen + 1);
+ if(rem <= 0) {
+ free(thename);
+ return CURLE_OUT_OF_MEMORY;
+ }
+ clen = *cp++;
+ }
+ *buf = cp;
+ if(rem <= 0) {
+ free(thename);
+ return CURLE_OUT_OF_MEMORY;
+ }
+ *remaining = rem - 1;
+ *dnsname = thename;
+ return CURLE_OK;
+}
+
+static CURLcode local_decode_rdata_alpn(unsigned char *rrval, size_t len,
+ char **alpns)
+{
+ /*
+ * spec here is as per draft-ietf-dnsop-svcb-https, section-7.1.1
+ * encoding is catenated list of strings each preceded by a one
+ * octet length
+ * output is comma-sep list of the strings
+ * implementations may or may not handle quoting of comma within
+ * string values, so we might see a comma within the wire format
+ * version of a string, in which case we'll precede that by a
+ * backslash - same goes for a backslash character, and of course
+ * we need to use two backslashes in strings when we mean one;-)
+ */
+ int remaining = (int) len;
+ char *oval;
+ size_t olen = 0, i;
+ unsigned char *cp = rrval;
+ struct dynbuf dval;
+
+ if(!alpns)
+ return CURLE_OUT_OF_MEMORY;
+ Curl_dyn_init(&dval, DYN_DOH_RESPONSE);
+ remaining = (int)len;
+ cp = rrval;
+ while(remaining > 0) {
+ size_t tlen = (size_t) *cp++;
+
+ /* if not 1st time, add comma */
+ if(remaining != (int)len && Curl_dyn_addn(&dval, ",", 1))
+ goto err;
+ remaining--;
+ if(tlen > (size_t)remaining)
+ goto err;
+ /* add escape char if needed, clunky but easier to read */
+ for(i = 0; i != tlen; i++) {
+ if('\\' == *cp || ',' == *cp) {
+ if(Curl_dyn_addn(&dval, "\\", 1))
+ goto err;
+ }
+ if(Curl_dyn_addn(&dval, cp++, 1))
+ goto err;
+ }
+ remaining -= (int)tlen;
+ }
+ olen = Curl_dyn_len(&dval);
+ /* I think the + 1 here is ok but it could trigger a read error */
+ oval = (char *)Curl_memdup(Curl_dyn_ptr(&dval), olen + 1);
+ if(!oval)
+ goto err;
+ Curl_dyn_free(&dval);
+ oval[olen]='\0';
+ *alpns = oval;
+ return CURLE_OK;
+err:
+ Curl_dyn_free(&dval);
+ return CURLE_BAD_CONTENT_ENCODING;
+}
+
+#ifdef CURLDEBUG
+static CURLcode test_alpn_escapes(void)
+{
+ /* we'll use an example from draft-ietf-dnsop-svcb, figure 10 */
+ static unsigned char example[] = {
+ 0x08, /* length 8 */
+ 0x66, 0x5c, 0x6f, 0x6f, 0x2c, 0x62, 0x61, 0x72, /* value "f\\oo,bar" */
+ 0x02, /* length 2 */
+ 0x68, 0x32 /* value "h2" */
+ };
+ size_t example_len = sizeof(example);
+ char *aval = NULL;
+ static const char *expected = "f\\\\oo\\,bar,h2";
+
+ if(local_decode_rdata_alpn(example, example_len, &aval) != CURLE_OK)
+ return CURLE_BAD_CONTENT_ENCODING;
+ if(strlen(aval) != strlen(expected))
+ return CURLE_BAD_CONTENT_ENCODING;
+ if(memcmp(aval, expected, strlen(aval)))
+ return CURLE_BAD_CONTENT_ENCODING;
+ return CURLE_OK;
+}
+#endif
+
+static CURLcode Curl_doh_decode_httpsrr(unsigned char *rrval, size_t len,
+ struct Curl_https_rrinfo **hrr)
+{
+ size_t remaining = len;
+ unsigned char *cp = rrval;
+ uint16_t pcode = 0, plen = 0;
+ struct Curl_https_rrinfo *lhrr = NULL;
+ char *dnsname = NULL;
+
+#ifdef CURLDEBUG
+ /* a few tests of escaping, shouldn't be here but ok for now */
+ if(test_alpn_escapes() != CURLE_OK)
+ return CURLE_OUT_OF_MEMORY;
+#endif
+ lhrr = calloc(1, sizeof(struct Curl_https_rrinfo));
+ if(!lhrr)
+ return CURLE_OUT_OF_MEMORY;
+ lhrr->val = calloc(1, len);
+ if(!lhrr->val)
+ goto err;
+ lhrr->len = len;
+ memcpy(lhrr->val, rrval, len);
+ if(remaining <= 2)
+ goto err;
+ lhrr->priority = (uint16_t)((cp[0] << 8) + cp[1]);
+ cp += 2;
+ remaining -= (uint16_t)2;
+ if(local_decode_rdata_name(&cp, &remaining, &dnsname) != CURLE_OK)
+ goto err;
+ lhrr->target = dnsname;
+ while(remaining >= 4) {
+ pcode = (uint16_t)((*cp << 8) + (*(cp + 1)));
+ cp += 2;
+ plen = (uint16_t)((*cp << 8) + (*(cp + 1)));
+ cp += 2;
+ remaining -= 4;
+ if(pcode == HTTPS_RR_CODE_ALPN) {
+ if(local_decode_rdata_alpn(cp, plen, &lhrr->alpns) != CURLE_OK)
+ goto err;
+ }
+ if(pcode == HTTPS_RR_CODE_NO_DEF_ALPN)
+ lhrr->no_def_alpn = TRUE;
+ else if(pcode == HTTPS_RR_CODE_IPV4) {
+ lhrr->ipv4hints = Curl_memdup(cp, plen);
+ if(!lhrr->ipv4hints)
+ goto err;
+ lhrr->ipv4hints_len = (size_t)plen;
+ }
+ else if(pcode == HTTPS_RR_CODE_ECH) {
+ lhrr->echconfiglist = Curl_memdup(cp, plen);
+ if(!lhrr->echconfiglist)
+ goto err;
+ lhrr->echconfiglist_len = (size_t)plen;
+ }
+ else if(pcode == HTTPS_RR_CODE_IPV6) {
+ lhrr->ipv6hints = Curl_memdup(cp, plen);
+ if(!lhrr->ipv6hints)
+ goto err;
+ lhrr->ipv6hints_len = (size_t)plen;
+ }
+ if(plen > 0 && plen <= remaining) {
+ cp += plen;
+ remaining -= plen;
+ }
+ }
+ DEBUGASSERT(!remaining);
+ *hrr = lhrr;
+ return CURLE_OK;
+err:
+ if(lhrr) {
+ if(lhrr->target)
+ free(lhrr->target);
+ if(lhrr->echconfiglist)
+ free(lhrr->echconfiglist);
+ if(lhrr->val)
+ free(lhrr->val);
+ free(lhrr);
+ }
+ return CURLE_OUT_OF_MEMORY;
+}
+
+# ifdef CURLDEBUG
+static void local_print_httpsrr(struct Curl_easy *data,
+ struct Curl_https_rrinfo *hrr)
+{
+ DEBUGASSERT(hrr);
+ infof(data, "HTTPS RR: priority %d, target: %s",
+ hrr->priority, hrr->target);
+ if(hrr->alpns)
+ infof(data, "HTTPS RR: alpns %s", hrr->alpns);
+ else
+ infof(data, "HTTPS RR: no alpns");
+ if(hrr->no_def_alpn)
+ infof(data, "HTTPS RR: no_def_alpn set");
+ else
+ infof(data, "HTTPS RR: no_def_alpn not set");
+ if(hrr->ipv4hints) {
+ local_print_buf(data, "HTTPS RR: ipv4hints",
+ hrr->ipv4hints, hrr->ipv4hints_len);
+ }
+ else
+ infof(data, "HTTPS RR: no ipv4hints");
+ if(hrr->echconfiglist) {
+ local_print_buf(data, "HTTPS RR: ECHConfigList",
+ hrr->echconfiglist, hrr->echconfiglist_len);
+ }
+ else
+ infof(data, "HTTPS RR: no ECHConfigList");
+ if(hrr->ipv6hints) {
+ local_print_buf(data, "HTTPS RR: ipv6hint",
+ hrr->ipv6hints, hrr->ipv6hints_len);
+ }
+ else
+ infof(data, "HTTPS RR: no ipv6hints");
+ return;
+}
+# endif
+#endif
+
CURLcode Curl_doh_is_resolved(struct Curl_easy *data,
struct Curl_dns_entry **dnsp)
{
CURLE_COULDNT_RESOLVE_HOST;
}
else if(!dohp->pending) {
+#ifndef USE_HTTPSRR
DOHcode rc[DOH_PROBE_SLOTS] = {
DOH_OK, DOH_OK
};
+#else
+ DOHcode rc[DOH_PROBE_SLOTS] = {
+ DOH_OK, DOH_OK, DOH_OK
+ };
+#endif
struct dohentry de;
int slot;
/* remove DoH handles from multi handle and close them */
} /* address processing done */
/* Now process any build-specific attributes retrieved from DNS */
+#ifdef USE_HTTPSRR
+ if(de.numhttps_rrs > 0 && result == CURLE_OK && *dnsp) {
+ struct Curl_https_rrinfo *hrr = NULL;
+ result = Curl_doh_decode_httpsrr(de.https_rrs->val, de.https_rrs->len,
+ &hrr);
+ if(result) {
+ infof(data, "Failed to decode HTTPS RR");
+ return result;
+ }
+ infof(data, "Some HTTPS RR to process");
+# ifdef CURLDEBUG
+ local_print_httpsrr(data, hrr);
+# endif
+ (*dnsp)->hinfo = hrr;
+ }
+#endif
/* All done */
de_cleanup(&de);
#include "urldata.h"
#include "curl_addrinfo.h"
+#ifdef USE_HTTPSRR
+# include <stdint.h>
+#endif
#ifndef CURL_DISABLE_DOH
DNS_TYPE_NS = 2,
DNS_TYPE_CNAME = 5,
DNS_TYPE_AAAA = 28,
- DNS_TYPE_DNAME = 39 /* RFC6672 */
+ DNS_TYPE_DNAME = 39, /* RFC6672 */
+ DNS_TYPE_HTTPS = 65
} DNStype;
/* one of these for each DoH request */
#define DOH_MAX_ADDR 24
#define DOH_MAX_CNAME 4
+#define DOH_MAX_HTTPS 4
struct dohaddr {
int type;
} ip;
};
+#ifdef USE_HTTPSRR
+
+/*
+ * These are the code points for DNS wire format SvcParams as
+ * per draft-ietf-dnsop-svcb-https
+ * Not all are supported now, and even those that are may need
+ * more work in future to fully support the spec.
+ */
+#define HTTPS_RR_CODE_ALPN 0x01
+#define HTTPS_RR_CODE_NO_DEF_ALPN 0x02
+#define HTTPS_RR_CODE_PORT 0x03
+#define HTTPS_RR_CODE_IPV4 0x04
+#define HTTPS_RR_CODE_ECH 0x05
+#define HTTPS_RR_CODE_IPV6 0x06
+
+/*
+ * These may need escaping when found within an alpn string
+ * value.
+ */
+#define COMMA_CHAR ','
+#define BACKSLASH_CHAR '\\'
+
+struct dohhttps_rr {
+ uint16_t len; /* raw encoded length */
+ unsigned char *val; /* raw encoded octets */
+};
+#endif
+
struct dohentry {
struct dynbuf cname[DOH_MAX_CNAME];
struct dohaddr addr[DOH_MAX_ADDR];
int numaddr;
unsigned int ttl;
int numcname;
+#ifdef USE_HTTPSRR
+ struct dohhttps_rr https_rrs[DOH_MAX_HTTPS];
+ int numhttps_rrs;
+#endif
};
{"DOH_SSL_VERIFYPEER", CURLOPT_DOH_SSL_VERIFYPEER, CURLOT_LONG, 0},
{"DOH_SSL_VERIFYSTATUS", CURLOPT_DOH_SSL_VERIFYSTATUS, CURLOT_LONG, 0},
{"DOH_URL", CURLOPT_DOH_URL, CURLOT_STRING, 0},
+ {"ECH", CURLOPT_ECH, CURLOT_STRING, 0},
{"EGDSOCKET", CURLOPT_EGDSOCKET, CURLOT_STRING, 0},
{"ENCODING", CURLOPT_ACCEPT_ENCODING, CURLOT_STRING, CURLOT_FLAG_ALIAS},
{"ERRORBUFFER", CURLOPT_ERRORBUFFER, CURLOT_OBJECT, 0},
*/
int Curl_easyopts_check(void)
{
- return ((CURLOPT_LASTENTRY%10000) != (324 + 1));
+ return ((CURLOPT_LASTENTRY%10000) != (325 + 1));
}
#endif
dns->inuse--;
if(dns->inuse == 0) {
Curl_freeaddrinfo(dns->addr);
+#ifdef USE_HTTPSRR
+ if(dns->hinfo) {
+ if(dns->hinfo->target)
+ free(dns->hinfo->target);
+ if(dns->hinfo->alpns)
+ free(dns->hinfo->alpns);
+ if(dns->hinfo->ipv4hints)
+ free(dns->hinfo->ipv4hints);
+ if(dns->hinfo->echconfiglist)
+ free(dns->hinfo->echconfiglist);
+ if(dns->hinfo->ipv6hints)
+ free(dns->hinfo->ipv6hints);
+ if(dns->hinfo->val)
+ free(dns->hinfo->val);
+ free(dns->hinfo);
+ }
+#endif
free(dns);
}
}
#include <setjmp.h>
+#ifdef USE_HTTPSRR
+# include <stdint.h>
+#endif
+
/* Allocate enough memory to hold the full name information structs and
* everything. OSF1 is known to require at least 8872 bytes. The buffer
* required for storing all possible aliases and IP numbers is according to
*/
struct Curl_hash *Curl_global_host_cache_init(void);
+#ifdef USE_HTTPSRR
+
+#define CURL_MAXLEN_host_name 253
+
+struct Curl_https_rrinfo {
+ size_t len; /* raw encoded length */
+ unsigned char *val; /* raw encoded octets */
+ /*
+ * fields from HTTPS RR, with the mandatory fields
+ * first (priority, target), then the others in the
+ * order of the keytag numbers defined at
+ * https://datatracker.ietf.org/doc/html/rfc9460#section-14.3.2
+ */
+ uint16_t priority;
+ char *target;
+ char *alpns; /* keytag = 1 */
+ bool no_def_alpn; /* keytag = 2 */
+ /*
+ * we don't support ports (keytag = 3) as we don't support
+ * port-switching yet
+ */
+ unsigned char *ipv4hints; /* keytag = 4 */
+ size_t ipv4hints_len;
+ unsigned char *echconfiglist; /* keytag = 5 */
+ size_t echconfiglist_len;
+ unsigned char *ipv6hints; /* keytag = 6 */
+ size_t ipv6hints_len;
+};
+#endif
+
struct Curl_dns_entry {
struct Curl_addrinfo *addr;
+#ifdef USE_HTTPSRR
+ struct Curl_https_rrinfo *hinfo;
+#endif
/* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (doesn't time out) */
time_t timestamp;
/* use-counter, use Curl_resolv_unlock to release reference */
data->set.ws_raw_mode = raw;
break;
}
+#endif
+#ifdef USE_ECH
+ case CURLOPT_ECH: {
+ size_t plen = 0;
+
+ argptr = va_arg(param, char *);
+ if(!argptr) {
+ data->set.tls_ech = CURLECH_DISABLE;
+ result = CURLE_BAD_FUNCTION_ARGUMENT;
+ return result;
+ }
+ plen = strlen(argptr);
+ if(plen > CURL_MAX_INPUT_LENGTH) {
+ data->set.tls_ech = CURLECH_DISABLE;
+ result = CURLE_BAD_FUNCTION_ARGUMENT;
+ return result;
+ }
+ /* set tls_ech flag value, preserving CLA_CFG bit */
+ if(plen == 5 && !strcmp(argptr, "false"))
+ data->set.tls_ech = CURLECH_DISABLE
+ | (data->set.tls_ech & CURLECH_CLA_CFG);
+ else if(plen == 6 && !strcmp(argptr, "grease"))
+ data->set.tls_ech = CURLECH_GREASE
+ | (data->set.tls_ech & CURLECH_CLA_CFG);
+ else if(plen == 4 && !strcmp(argptr, "true"))
+ data->set.tls_ech = CURLECH_ENABLE
+ | (data->set.tls_ech & CURLECH_CLA_CFG);
+ else if(plen == 4 && !strcmp(argptr, "hard"))
+ data->set.tls_ech = CURLECH_HARD
+ | (data->set.tls_ech & CURLECH_CLA_CFG);
+ else if(plen > 5 && !strncmp(argptr, "ecl:", 4)) {
+ result = Curl_setstropt(&data->set.str[STRING_ECH_CONFIG], argptr + 4);
+ if(result)
+ return result;
+ data->set.tls_ech |= CURLECH_CLA_CFG;
+ }
+ else if(plen > 4 && !strncmp(argptr, "pn:", 3)) {
+ result = Curl_setstropt(&data->set.str[STRING_ECH_PUBLIC], argptr + 3);
+ if(result)
+ return result;
+ }
+ break;
+ }
#endif
case CURLOPT_QUICK_EXIT:
data->set.quick_exit = (0 != va_arg(param, long)) ? 1L:0L;
case CURLE_TOO_LARGE:
return "A value or data field grew larger than allowed";
+ case CURLE_ECH_REQUIRED:
+ return "ECH attempted but failed";
+
/* error codes not used by current libcurl */
case CURLE_OBSOLETE20:
case CURLE_OBSOLETE24:
struct curl_trc_featt;
+#ifdef USE_ECH
+/* CURLECH_ bits for the tls_ech option */
+# define CURLECH_DISABLE (1<<0)
+# define CURLECH_GREASE (1<<1)
+# define CURLECH_ENABLE (1<<2)
+# define CURLECH_HARD (1<<3)
+# define CURLECH_CLA_CFG (1<<4)
+#endif
+
#ifdef USE_WEBSOCKETS
/* CURLPROTO_GOPHERS (29) is the highest publicly used protocol bit number,
* the rest are internal information. If we use higher bits we only do this on
DOH_PROBE_SLOT_IPADDR_V6 = 1, /* 'V6' likewise */
/* Space here for (possibly build-specific) additional slot definitions */
+#ifdef USE_HTTPSRR
+ DOH_PROBE_SLOT_HTTPS = 2, /* for HTTPS RR */
+#endif
/* for example */
/* #ifdef WANT_DOH_FOOBAR_TXT */
#ifndef CURL_DISABLE_PROXY
STRING_HAPROXY_CLIENT_IP, /* CURLOPT_HAPROXY_CLIENT_IP */
#endif
+ STRING_ECH_CONFIG, /* CURLOPT_ECH_CONFIG */
+ STRING_ECH_PUBLIC, /* CURLOPT_ECH_PUBLIC */
/* -- end of null-terminated strings -- */
#ifdef USE_WEBSOCKETS
BIT(ws_raw_mode);
#endif
+#ifdef USE_ECH
+ int tls_ech; /* TLS ECH configuration */
+#endif
};
#ifndef CURL_DISABLE_MIME
#include <openssl/tls1.h>
#include <openssl/evp.h>
+#ifdef USE_ECH
+# ifndef OPENSSL_IS_BORINGSSL
+# include <openssl/ech.h>
+# endif
+# include "curl_base64.h"
+# define ECH_ENABLED(__data__) \
+ (__data__->set.tls_ech && \
+ !(__data__->set.tls_ech & CURLECH_DISABLE)\
+ )
+#endif /* USE_ECH */
+
#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_OCSP)
#include <openssl/ocsp.h>
#endif
const char * const ssl_cert_type = ssl_config->cert_type;
const bool verifypeer = conn_config->verifypeer;
char error_buffer[256];
+#ifdef USE_ECH
+ struct ssl_connect_data *connssl = cf->ctx;
+#endif
/* Make funny stuff to get random input */
result = ossl_seed(data);
return CURLE_SSL_CONNECT_ERROR;
}
}
+
+#ifdef USE_ECH
+ if(ECH_ENABLED(data)) {
+ unsigned char *ech_config = NULL;
+ size_t ech_config_len = 0;
+ char *outername = data->set.str[STRING_ECH_PUBLIC];
+ int trying_ech_now = 0;
+
+ if(data->set.tls_ech & CURLECH_GREASE) {
+ infof(data, "ECH: will GREASE ClientHello");
+# ifdef OPENSSL_IS_BORINGSSL
+ SSL_set_enable_ech_grease(octx->ssl, 1);
+# else
+ SSL_set_options(octx->ssl, SSL_OP_ECH_GREASE);
+# endif
+ }
+ else if(data->set.tls_ech & CURLECH_CLA_CFG) {
+# ifdef OPENSSL_IS_BORINGSSL
+ /* have to do base64 decode here for boring */
+ const char *b64 = data->set.str[STRING_ECH_CONFIG];
+
+ if(!b64) {
+ infof(data, "ECH: ECHConfig from command line empty");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ ech_config_len = 2 * strlen(b64);
+ result = Curl_base64_decode(b64, &ech_config, &ech_config_len);
+ if(result || !ech_config) {
+ infof(data, "ECH: can't base64 decode ECHConfig from command line");
+ if(data->set.tls_ech & CURLECH_HARD)
+ return result;
+ }
+ if(SSL_set1_ech_config_list(octx->ssl, ech_config,
+ ech_config_len) != 1) {
+ infof(data, "ECH: SSL_ECH_set1_echconfig failed");
+ if(data->set.tls_ech & CURLECH_HARD) {
+ free(ech_config);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+ free(ech_config);
+ trying_ech_now = 1;
+# else
+ ech_config = (unsigned char *) data->set.str[STRING_ECH_CONFIG];
+ if(!ech_config) {
+ infof(data, "ECH: ECHConfig from command line empty");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ ech_config_len = strlen(data->set.str[STRING_ECH_CONFIG]);
+ if(SSL_ech_set1_echconfig(octx->ssl, ech_config, ech_config_len) != 1) {
+ infof(data, "ECH: SSL_ECH_set1_echconfig failed");
+ if(data->set.tls_ech & CURLECH_HARD)
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ else
+ trying_ech_now = 1;
+# endif
+ infof(data, "ECH: ECHConfig from command line");
+ }
+ else {
+ struct Curl_dns_entry *dns = NULL;
+
+ dns = Curl_fetch_addr(data, connssl->peer.hostname, connssl->peer.port);
+ if(!dns) {
+ infof(data, "ECH: requested but no DNS info available");
+ if(data->set.tls_ech & CURLECH_HARD)
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ else {
+ struct Curl_https_rrinfo *rinfo = NULL;
+
+ rinfo = dns->hinfo;
+ if(rinfo && rinfo->echconfiglist) {
+ unsigned char *ecl = rinfo->echconfiglist;
+ size_t elen = rinfo->echconfiglist_len;
+
+ infof(data, "ECH: ECHConfig from DoH HTTPS RR");
+# ifndef OPENSSL_IS_BORINGSSL
+ if(SSL_ech_set1_echconfig(octx->ssl, ecl, elen) != 1) {
+ infof(data, "ECH: SSL_ECH_set1_echconfig failed");
+ if(data->set.tls_ech & CURLECH_HARD)
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+# else
+ if(SSL_set1_ech_config_list(octx->ssl, ecl, elen) != 1) {
+ infof(data, "ECH: SSL_set1_ech_config_list failed (boring)");
+ if(data->set.tls_ech & CURLECH_HARD)
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+# endif
+ else {
+ trying_ech_now = 1;
+ infof(data, "ECH: imported ECHConfigList of length %ld", elen);
+ }
+ }
+ else {
+ infof(data, "ECH: requested but no ECHConfig available");
+ if(data->set.tls_ech & CURLECH_HARD)
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ Curl_resolv_unlock(data, dns);
+ }
+ }
+# ifdef OPENSSL_IS_BORINGSSL
+ if(trying_ech_now && outername) {
+ infof(data, "ECH: setting public_name not supported with boringssl");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+# else
+ if(trying_ech_now && outername) {
+ infof(data, "ECH: inner: '%s', outer: '%s'",
+ connssl->peer.hostname, outername);
+ result = SSL_ech_set_server_names(octx->ssl,
+ connssl->peer.hostname, outername,
+ 0 /* do send outer */);
+ if(result != 1) {
+ infof(data, "ECH: rv failed to set server name(s) %d [ERROR]", result);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+# endif /* not BORING */
+ if(trying_ech_now
+ && SSL_set_min_proto_version(octx->ssl, TLS1_3_VERSION) != 1) {
+ infof(data, "ECH: Can't force TLSv1.3 [ERROR]");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+#endif /* USE_ECH */
+
#endif
octx->reused_session = FALSE;
return CURLE_OK;
}
+#ifdef USE_ECH
+/* If we have retry configs, then trace those out */
+static void ossl_trace_ech_retry_configs(struct Curl_easy *data, SSL* ssl,
+ int reason)
+{
+ CURLcode result = CURLE_OK;
+ size_t rcl = 0;
+ int rv = 1;
+# ifndef OPENSSL_IS_BORINGSSL
+ char *inner = NULL;
+ unsigned char *rcs = NULL;
+ char *outer = NULL;
+# else
+ const char *inner = NULL;
+ const uint8_t *rcs = NULL;
+ const char *outer = NULL;
+ size_t out_name_len = 0;
+ int servername_type = 0;
+# endif
+
+ /* nothing to trace if not doing ECH */
+ if(!ECH_ENABLED(data))
+ return;
+# ifndef OPENSSL_IS_BORINGSSL
+ rv = SSL_ech_get_retry_config(ssl, &rcs, &rcl);
+# else
+ SSL_get0_ech_retry_configs(ssl, &rcs, &rcl);
+ rv = (int)rcl;
+# endif
+
+ if(rv && rcs) {
+# define HEXSTR_MAX 800
+ char *b64str = NULL;
+ size_t blen = 0;
+
+ result = Curl_base64_encode((const char *)rcs, rcl,
+ &b64str, &blen);
+ if(!result && b64str)
+ infof(data, "ECH: retry_configs %s", b64str);
+ free(b64str);
+# ifndef OPENSSL_IS_BORINGSSL
+ rv = SSL_ech_get_status(ssl, &inner, &outer);
+ infof(data, "ECH: retry_configs for %s from %s, %d %d",
+ inner ? inner : "NULL", outer ? outer : "NULL", reason, rv);
+#else
+ rv = SSL_ech_accepted(ssl);
+ servername_type = SSL_get_servername_type(ssl);
+ inner = SSL_get_servername(ssl, servername_type);
+ SSL_get0_ech_name_override(ssl, &outer, &out_name_len);
+ /* TODO: get the inner from boring */
+ infof(data, "ECH: retry_configs for %s from %s, %d %d",
+ inner ? inner : "NULL", outer ? outer : "NULL", reason, rv);
+#endif
+ }
+ else
+ infof(data, "ECH: no retry_configs (rv = %d)", rv);
+# ifndef OPENSSL_IS_BORINGSSL
+ OPENSSL_free((void *)rcs);
+# endif
+ return;
+}
+
+#endif
+
static CURLcode ossl_connect_step2(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
result = CURLE_SSL_CLIENTCERT;
ossl_strerror(errdetail, error_buffer, sizeof(error_buffer));
}
+#endif
+#ifdef USE_ECH
+ else if((lib == ERR_LIB_SSL) &&
+# ifndef OPENSSL_IS_BORINGSSL
+ (reason == SSL_R_ECH_REQUIRED)) {
+# else
+ (reason == SSL_R_ECH_REJECTED)) {
+# endif
+
+ /* trace retry_configs if we got some */
+ ossl_trace_ech_retry_configs(data, octx->ssl, reason);
+
+ result = CURLE_ECH_REQUIRED;
+ ossl_strerror(errdetail, error_buffer, sizeof(error_buffer));
+ }
#endif
else {
result = CURLE_SSL_CONNECT_ERROR;
negotiated_group_name? negotiated_group_name : "[blank]",
OBJ_nid2sn(psigtype_nid));
+#ifdef USE_ECH
+# ifndef OPENSSL_IS_BORINGSSL
+ if(ECH_ENABLED(data)) {
+ char *inner = NULL, *outer = NULL;
+ const char *status = NULL;
+ int rv;
+
+ rv = SSL_ech_get_status(octx->ssl, &inner, &outer);
+ switch(rv) {
+ case SSL_ECH_STATUS_SUCCESS:
+ status = "succeeded";
+ break;
+ case SSL_ECH_STATUS_GREASE_ECH:
+ status = "sent GREASE, got retry-configs";
+ break;
+ case SSL_ECH_STATUS_GREASE:
+ status = "sent GREASE";
+ break;
+ case SSL_ECH_STATUS_NOT_TRIED:
+ status = "not attempted";
+ break;
+ case SSL_ECH_STATUS_NOT_CONFIGURED:
+ status = "not configured";
+ break;
+ case SSL_ECH_STATUS_BACKEND:
+ status = "backend (unexpected)";
+ break;
+ case SSL_ECH_STATUS_FAILED:
+ status = "failed";
+ break;
+ case SSL_ECH_STATUS_BAD_CALL:
+ status = "bad call (unexpected)";
+ break;
+ case SSL_ECH_STATUS_BAD_NAME:
+ status = "bad name (unexpected)";
+ break;
+ default:
+ status = "unexpected status";
+ infof(data, "ECH: unexpected status %d",rv);
+ }
+ infof(data, "ECH: result: status is %s, inner is %s, outer is %s",
+ (status?status:"NULL"),
+ (inner?inner:"NULL"),
+ (outer?outer:"NULL"));
+ OPENSSL_free(inner);
+ OPENSSL_free(outer);
+ if(rv == SSL_ECH_STATUS_GREASE_ECH) {
+ /* trace retry_configs if we got some */
+ ossl_trace_ech_retry_configs(data, octx->ssl, 0);
+ }
+ if(rv != SSL_ECH_STATUS_SUCCESS
+ && data->set.tls_ech & CURLECH_HARD) {
+ infof(data, "ECH: ech-hard failed");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+ else {
+ infof(data, "ECH: result: status is not attempted");
+ }
+# endif /* BORING */
+#endif /* USE_ECH */
+
#ifdef HAS_ALPN
/* Sets data and len to negotiated protocol, len is 0 if no protocol was
* negotiated
#include "curl_memory.h"
#include "memdebug.h"
+#ifdef USE_ECH
+# include "curl_base64.h"
+# define ECH_ENABLED(__data__) \
+ (__data__->set.tls_ech && \
+ !(__data__->set.tls_ech & CURLECH_DISABLE)\
+ )
+#endif /* USE_ECH */
+
/* KEEP_PEER_CERT is a product of the presence of build time symbol
OPENSSL_EXTRA without NO_CERTS, depending on the version. KEEP_PEER_CERT is
in wolfSSL's settings.h, and the latter two are build time symbols in
Curl_ssl_sessionid_unlock(data);
}
+#ifdef USE_ECH
+ if(ECH_ENABLED(data)) {
+ int trying_ech_now = 0;
+
+ if(data->set.str[STRING_ECH_PUBLIC]) {
+ infof(data, "ECH: outername not (yet) supported with WolfSSL");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ if(data->set.tls_ech == CURLECH_GREASE) {
+ infof(data, "ECH: GREASE'd ECH not yet supported for wolfSSL");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ if(data->set.tls_ech & CURLECH_CLA_CFG
+ && data->set.str[STRING_ECH_CONFIG]) {
+ char *b64val = data->set.str[STRING_ECH_CONFIG];
+ word32 b64len = 0;
+
+ b64len = (word32) strlen(b64val);
+ if(b64len
+ && wolfSSL_SetEchConfigsBase64(backend->handle, b64val, b64len)
+ != WOLFSSL_SUCCESS) {
+ if(data->set.tls_ech & CURLECH_HARD)
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ else {
+ trying_ech_now = 1;
+ infof(data, "ECH: ECHConfig from command line");
+ }
+ }
+ else {
+ struct Curl_dns_entry *dns = NULL;
+
+ dns = Curl_fetch_addr(data, connssl->peer.hostname, connssl->peer.port);
+ if(!dns) {
+ infof(data, "ECH: requested but no DNS info available");
+ if(data->set.tls_ech & CURLECH_HARD)
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ else {
+ struct Curl_https_rrinfo *rinfo = NULL;
+
+ rinfo = dns->hinfo;
+ if(rinfo && rinfo->echconfiglist) {
+ unsigned char *ecl = rinfo->echconfiglist;
+ size_t elen = rinfo->echconfiglist_len;
+
+ infof(data, "ECH: ECHConfig from DoH HTTPS RR");
+ if(wolfSSL_SetEchConfigs(backend->handle, ecl, (word32) elen) !=
+ WOLFSSL_SUCCESS) {
+ infof(data, "ECH: wolfSSL_SetEchConfigs failed");
+ if(data->set.tls_ech & CURLECH_HARD)
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ else {
+ trying_ech_now = 1;
+ infof(data, "ECH: imported ECHConfigList of length %ld", elen);
+ }
+ }
+ else {
+ infof(data, "ECH: requested but no ECHConfig available");
+ if(data->set.tls_ech & CURLECH_HARD)
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ Curl_resolv_unlock(data, dns);
+ }
+ }
+
+ if(trying_ech_now
+ && SSL_set_min_proto_version(backend->handle, TLS1_3_VERSION) != 1) {
+ infof(data, "ECH: Can't force TLSv1.3 [ERROR]");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ }
+#endif /* USE_ECH */
+
#ifdef USE_BIO_CHAIN
{
WOLFSSL_BIO *bio;
"continuing anyway");
}
}
+#endif
+#ifdef USE_ECH
+ else if(-1 == detail) {
+ /* try access a retry_config ECHConfigList for tracing */
+ byte echConfigs[1000];
+ word32 echConfigsLen = 1000;
+ int rv = 0;
+
+ /* this currently doesn't produce the retry_configs */
+ rv = wolfSSL_GetEchConfigs(backend->handle, echConfigs,
+ &echConfigsLen);
+ if(rv != WOLFSSL_SUCCESS) {
+ infof(data, "Failed to get ECHConfigs");
+ }
+ else {
+ char *b64str = NULL;
+ size_t blen = 0;
+
+ rv = Curl_base64_encode((const char *)echConfigs, echConfigsLen,
+ &b64str, &blen);
+ if(!rv && b64str)
+ infof(data, "ECH: (not yet) retry_configs %s", b64str);
+ free(b64str);
+ }
+ }
#endif
else if(backend->io_result == CURLE_AGAIN) {
return CURLE_OK;
fi
])
+dnl CURL_CHECK_OPTION_NTLM_WB
+dnl -------------------------------------------------
+dnl Verify if configure has been invoked with option
+dnl --enable-ntlm-wb or --disable-ntlm-wb, and set
+dnl shell variable want_ntlm_wb and want_ntlm_wb_file
+dnl as appropriate.
+
+AC_DEFUN([CURL_CHECK_OPTION_NTLM_WB], [
+ AC_BEFORE([$0],[CURL_CHECK_NTLM_WB])dnl
+ OPT_NTLM_WB="default"
+ AC_ARG_ENABLE(ntlm-wb,
+AS_HELP_STRING([--enable-ntlm-wb@<:@=FILE@:>@],[Enable NTLM delegation to winbind's ntlm_auth helper, where FILE is ntlm_auth's absolute filename (default: /usr/bin/ntlm_auth)])
+AS_HELP_STRING([--disable-ntlm-wb],[Disable NTLM delegation to winbind's ntlm_auth helper]),
+ OPT_NTLM_WB=$enableval)
+ want_ntlm_wb_file="/usr/bin/ntlm_auth"
+ case "$OPT_NTLM_WB" in
+ no)
+ dnl --disable-ntlm-wb option used
+ want_ntlm_wb="no"
+ ;;
+ default)
+ dnl configure option not specified
+ want_ntlm_wb="yes"
+ ;;
+ *)
+ dnl --enable-ntlm-wb option used
+ want_ntlm_wb="yes"
+ if test -n "$enableval" && test "$enableval" != "yes"; then
+ want_ntlm_wb_file="$enableval"
+ fi
+ ;;
+ esac
+])
+
+
+dnl CURL_CHECK_NTLM_WB
+dnl -------------------------------------------------
+dnl Check if support for NTLM delegation to winbind's
+dnl ntlm_auth helper will finally be enabled depending
+dnl on given configure options and target platform.
+
+AC_DEFUN([CURL_CHECK_NTLM_WB], [
+ AC_REQUIRE([CURL_CHECK_OPTION_NTLM_WB])dnl
+ AC_REQUIRE([CURL_CHECK_NATIVE_WINDOWS])dnl
+ AC_MSG_CHECKING([whether to enable NTLM delegation to winbind's helper])
+ if test "$curl_cv_native_windows" = "yes" ||
+ test "x$SSL_ENABLED" = "x"; then
+ want_ntlm_wb_file=""
+ want_ntlm_wb="no"
+ elif test "x$ac_cv_func_fork" != "xyes"; then
+ dnl ntlm_wb requires fork
+ want_ntlm_wb="no"
+ fi
+ AC_MSG_RESULT([$want_ntlm_wb])
+ if test "$want_ntlm_wb" = "yes"; then
+ AC_DEFINE(NTLM_WB_ENABLED, 1,
+ [Define to enable NTLM delegation to winbind's ntlm_auth helper.])
+ AC_DEFINE_UNQUOTED(NTLM_WB_FILE, "$want_ntlm_wb_file",
+ [Define absolute filename for winbind's ntlm_auth helper.])
+ NTLM_WB_ENABLED=1
+ fi
+])
+
+dnl CURL_CHECK_OPTION_HTTPSRR
+dnl -----------------------------------------------------
+dnl Verify whether configure has been invoked with option
+dnl --enable-httpsrr or --disable-httpsrr, and set
+dnl shell variable want_httpsrr as appropriate.
+
+AC_DEFUN([CURL_CHECK_OPTION_HTTPSRR], [
+ AC_MSG_CHECKING([whether to enable HTTPSRR support])
+ OPT_HTTPSRR="default"
+ AC_ARG_ENABLE(httpsrr,
+AS_HELP_STRING([--enable-httpsrr],[Enable HTTPSRR support])
+AS_HELP_STRING([--disable-httpsrr],[Disable HTTPSRR support]),
+ OPT_HTTPSRR=$enableval)
+ case "$OPT_HTTPSRR" in
+ no)
+ dnl --disable-httpsrr option used
+ want_httpsrr="no"
+ curl_httpsrr_msg="no (--enable-httpsrr)"
+ AC_MSG_RESULT([no])
+ ;;
+ default)
+ dnl configure option not specified
+ want_httpsrr="no"
+ curl_httpsrr_msg="no (--enable-httpsrr)"
+ AC_MSG_RESULT([no])
+ ;;
+ *)
+ dnl --enable-httpsrr option used
+ want_httpsrr="yes"
+ curl_httpsrr_msg="enabled (--disable-httpsrr)"
+ experimental="httpsrr"
+ AC_MSG_RESULT([yes])
+ ;;
+ esac
+])
+
dnl CURL_CHECK_OPTION_ECH
dnl -----------------------------------------------------
dnl Verify whether configure has been invoked with option
;;
esac
])
+])
case CURLOPT_DNS_LOCAL_IP6:
case CURLOPT_DNS_SERVERS:
case CURLOPT_DOH_URL:
+#ifdef USE_ECH
+ case CURLOPT_ECH:
+#endif
case CURLOPT_EGDSOCKET:
case CURLOPT_FTPPORT:
case CURLOPT_FTP_ACCOUNT:
Curl_safefree(config->aws_sigv4);
Curl_safefree(config->proto_str);
Curl_safefree(config->proto_redir_str);
+#ifdef USE_ECH
+ Curl_safefree(config->ech);
+ config->ech = NULL;
+ Curl_safefree(config->ech_config);
+ config->ech_config = NULL;
+ Curl_safefree(config->ech_public);
+ config->ech_public = NULL;
+#endif
}
void config_free(struct OperationConfig *config)
struct State state; /* for create_transfer() */
bool rm_partial; /* on error, remove partially written output
files */
+#ifdef USE_ECH
+ char *ech; /* Config set by --ech keywords */
+ char *ech_config; /* Config set by "--ech esl:" option */
+ char *ech_public; /* Config set by "--ech pn:" option */
+#endif
+
};
struct GlobalConfig {
C_DOH_INSECURE,
C_DOH_URL,
C_DUMP_HEADER,
+ C_ECH,
C_EGD_FILE,
C_ENGINE,
C_EPRT,
{"doh-insecure", ARG_BOOL, ' ', C_DOH_INSECURE},
{"doh-url" , ARG_STRG, ' ', C_DOH_URL},
{"dump-header", ARG_FILE, 'D', C_DUMP_HEADER},
+ {"ech", ARG_STRG, ' ', C_ECH},
{"egd-file", ARG_STRG, ' ', C_EGD_FILE},
{"engine", ARG_STRG, ' ', C_ENGINE},
{"eprt", ARG_BOOL, ' ', C_EPRT},
err = PARAM_ENGINES_REQUESTED;
}
break;
+#ifndef USE_ECH
+ case C_ECH: /* --ech, not implemented by default */
+ err = PARAM_LIBCURL_DOESNT_SUPPORT;
+ break;
+#else
+ case C_ECH: /* --ech */
+ if(strlen(nextarg) > 4 && strncasecompare("pn:", nextarg, 3)) {
+ /* a public_name */
+ err = getstr(&config->ech_public, nextarg, DENY_BLANK);
+ }
+ else if(strlen(nextarg) > 5 && strncasecompare("ecl:", nextarg, 4)) {
+ /* an ECHConfigList */
+ if('@' != *(nextarg + 4)) {
+ err = getstr(&config->ech_config, nextarg, DENY_BLANK);
+ }
+ else {
+ /* Indirect case: @filename or @- for stdin */
+ char *tmpcfg = NULL;
+ FILE *file;
+
+ nextarg++; /* skip over '@' */
+ if(!strcmp("-", nextarg)) {
+ file = stdin;
+ }
+ else {
+ file = fopen(nextarg, FOPEN_READTEXT);
+ }
+ if(!file) {
+ warnf(global,
+ "Couldn't read file \"%s\" "
+ "specified for \"--ech ecl:\" option",
+ nextarg);
+ return PARAM_BAD_USE; /* */
+ }
+ err = file2string(&tmpcfg, file);
+ if(file != stdin)
+ fclose(file);
+ if(err)
+ return err;
+ config->ech_config = aprintf("ecl:%s",tmpcfg);
+ if(!config->ech_config)
+ return PARAM_NO_MEM;
+ free(tmpcfg);
+ } /* file done */
+ }
+ else {
+ /* Simple case: just a string, with a keyword */
+ err = getstr(&config->ech, nextarg, DENY_BLANK);
+ }
+ break;
+#endif
case C_CAPATH: /* --capath */
err = getstr(&config->capath, nextarg, DENY_BLANK);
break;
{"telnet", "TELNET protocol options", CURLHELP_TELNET},
{"tftp", "TFTP protocol options", CURLHELP_TFTP},
{"tls", "All TLS/SSL related options", CURLHELP_TLS},
+ {"ech", "All Encrypted Client Hello (ECH) options", CURLHELP_ECH},
{"upload", "All options for uploads",
CURLHELP_UPLOAD},
{"verbose", "Options related to any kind of command line output of curl",
#define CURLHELP_TLS 1u << 22u
#define CURLHELP_UPLOAD 1u << 23u
#define CURLHELP_VERBOSE 1u << 24u
+#define CURLHELP_ECH 1u << 25u
extern const struct helptxt helptext[];
{"-D, --dump-header <filename>",
"Write the received headers to <filename>",
CURLHELP_HTTP | CURLHELP_FTP},
+ {" --ech <config>",
+ "Configure Encrypted Client Hello (ECH) for use with the TLS session",
+ CURLHELP_TLS | CURLHELP_ECH},
{" --egd-file <file>",
"EGD socket path for random data",
CURLHELP_TLS},
if(config->hsts)
my_setopt_str(curl, CURLOPT_HSTS, config->hsts);
+#ifdef USE_ECH
+ /* only if enabled in configure */
+ if(config->ech) /* only if set (optional) */
+ my_setopt_str(curl, CURLOPT_ECH, config->ech);
+ if(config->ech_public) /* only if set (optional) */
+ my_setopt_str(curl, CURLOPT_ECH, config->ech_public);
+ if(config->ech_config) /* only if set (optional) */
+ my_setopt_str(curl, CURLOPT_ECH, config->ech_config);
+#endif
+
/* initialize retry vars for loop below */
per->retry_sleep_default = (config->retry_delay) ?
config->retry_delay*1000L : RETRY_SLEEP_DEFAULT; /* ms */
telnet TELNET protocol options
tftp TFTP protocol options
tls All TLS/SSL related options
+ ech All Encrypted Client Hello (ECH) options
upload All options for uploads
verbose Options related to any kind of command line output of curl
</stdout>
e98: SSL Client Certificate required
e99: Unrecoverable error in select/poll
e100: A value or data field grew larger than allowed
-e101: Unknown error
+e101: ECH attempted but failed
+e102: Unknown error
m-1: Please call curl_multi_perform() soon
m0: No error
m1: Invalid multi handle
--- /dev/null
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#***************************************************************************
+# _ _ ____ _
+# Project ___| | | | _ \| |
+# / __| | | | |_) | |
+# | (__| |_| | _ <| |___
+# \___|\___/|_| \_\_____|
+#
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at https://curl.se/docs/copyright.html.
+#
+# You may opt to use, copy, modify, merge, publish, distribute and/or sell
+# copies of the Software, and permit persons to whom the Software is
+# furnished to do so, under the terms of the COPYING file.
+#
+# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+# KIND, either express or implied.
+#
+# SPDX-License-Identifier: curl
+#
+###########################################################################
+#
+# Python3 program to print all combination of size r in an array of size n.
+# This is used to generate test lines in tests/ech_test.sh.
+# This will be discarded in the process of moving from experimental,
+# but is worth preserving for the moment in case of changes to the
+# ECH command line args
+
+def CombinationRepetitionUtil(chosen, arr, badarr, index,
+ r, start, end):
+
+ # Current combination is ready,
+ # print it
+ if index == r:
+ # figure out if result should be good or bad and
+ # print prefix, assuming $turl does support ECH so
+ # should work if given "positive" parameters
+ res = 1
+ j = len(chosen) - 1
+ while res and j >= 0:
+ if chosen[j] in badarr:
+ res = 0
+ j = j - 1
+ print("cli_test $turl 1", res, end = " ")
+ # print combination but eliminating any runs of
+ # two identical params
+ for j in range(r):
+ if j != 0 and chosen[j] != chosen[j-1]:
+ print(chosen[j], end = " ")
+
+ print()
+ return
+
+ # When no more elements are
+ # there to put in chosen[]
+ if start > n:
+ return
+
+ # Current is included, put
+ # next at next location
+ chosen[index] = arr[start]
+
+ # Current is excluded, replace it
+ # with next (Note that i+1 is passed,
+ # but index is not changed)
+ CombinationRepetitionUtil(chosen, arr, badarr, index + 1,
+ r, start, end)
+ CombinationRepetitionUtil(chosen, arr, badarr, index,
+ r, start + 1, end)
+
+# The main function that prints all
+# combinations of size r in arr[] of
+# size n. This function mainly uses
+# CombinationRepetitionUtil()
+def CombinationRepetition(arr, badarr, n, r):
+
+ # A temporary array to store
+ # all combination one by one
+ chosen = [0] * r
+
+ # Print all combination using
+ # temporary array 'chosen[]'
+ CombinationRepetitionUtil(chosen, arr, badarr, 0, r, 0, n)
+
+# Driver code
+badarr = [ '--ech grease', '--ech false', '--ech ecl:$badecl', '--ech pn:$badpn' ]
+goodarr = [ '--ech hard', '--ech true', '--ech ecl:$goodecl', '--ech pn:$goodpn' ]
+arr = badarr + goodarr
+r = 8
+n = len(arr) - 1
+
+CombinationRepetition(arr, badarr, n, r)
+
+# This code is contributed by Vaibhav Kumar 12.
+
--- /dev/null
+#!/bin/bash
+#***************************************************************************
+# _ _ ____ _
+# Project ___| | | | _ \| |
+# / __| | | | |_) | |
+# | (__| |_| | _ <| |___
+# \___|\___/|_| \_\_____|
+#
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at https://curl.se/docs/copyright.html.
+#
+# You may opt to use, copy, modify, merge, publish, distribute and/or sell
+# copies of the Software, and permit persons to whom the Software is
+# furnished to do so, under the terms of the COPYING file.
+#
+# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+# KIND, either express or implied.
+#
+# SPDX-License-Identifier: curl
+#
+###########################################################################
+#
+
+# Run some tests against servers we know to support ECH (CF, defo.ie, etc.).
+# as well as some we know don't do ECH but have an HTTPS RR, and finally some
+# for which neither is the case.
+
+# TODO: Translate this into something that approximates a valid curl test:-)
+# Should be useful though even before such translation and a pile less work
+# to do this than that. The pile of work required would include making an
+# ECH-enabled server and a DoH server. For now, this is just run manually.
+#
+
+# set -x
+
+# Exit with an error if there's an active ech stanza in ~/.curlrc
+# as that'd likely skew some results (e.g. turning a fail into a
+# success or vice versa)
+: "${CURL_CFG_FILE=$HOME/.curlrc}"
+active_ech=$(grep ech "$CURL_CFG_FILE" | grep -v "#.*ech")
+if [[ "$active_ech" != "" ]]
+then
+ echo "You seem to have an active ECH setting in $CURL_CFG_FILE"
+ echo "That might affect results so please remove that or comment"
+ echo "it out - exiting."
+ exit 1
+fi
+
+
+# Targets we expect to be ECH-enabled servers
+# for which an HTTPS RR is published.
+# structure is host:port mapped to pathname
+# TODO: add negative tests for these
+declare -A ech_targets=(
+ [my-own.net]="ech-check.php"
+ [my-own.net:8443]="ech-check.php"
+ [defo.ie]="ech-check.php"
+ [cover.defo.ie]=""
+ [draft-13.esni.defo.ie:8413]="stats"
+ [draft-13.esni.defo.ie:8414]="stats"
+ [draft-13.esni.defo.ie:9413]=""
+ [draft-13.esni.defo.ie:10413]=""
+ [draft-13.esni.defo.ie:11413]=""
+ [draft-13.esni.defo.ie:12413]=""
+ [draft-13.esni.defo.ie:12414]=""
+ [crypto.cloudflare.com]="cdn-cgi/trace"
+ [tls-ech.dev]=""
+ [epochbelt.com]=""
+)
+
+# Targets we expect not to be ECH-enabled servers
+# but for which an HTTPS RR is published.
+declare -A httpsrr_targets=(
+ [ietf.org]=""
+ [rte.ie]=""
+)
+
+# Targets we expect not to be ECH-enabled servers
+# and for which no HTTPS RR is published.
+declare -A neither_targets=(
+ [www.tcd.ie]=""
+ [jell.ie]=""
+)
+
+#
+# Variables that can be over-ridden from environment
+#
+
+# Top of curl test tree, assume we're there
+: "${CTOP:=.}"
+
+# Plase to put test log output
+: "${LTOP:=$CTOP/tests/ech-log/}"
+
+# place to stash outputs when things go wrong
+: "${BTOP:=$LTOP}"
+
+# time to wait for a remote access to work, 10 seconds
+: "${tout:=10s}"
+
+# Where we find OpenSSL .so's
+: "${OSSL:=$HOME/code/openssl}"
+
+# Where we find WolfSSL .so's
+: "${WSSL:=$HOME/code/wolfssl/inst/lib}"
+
+# Where we find boringssl .so's
+: "${BSSL:=$HOME/code/boringssl/inst/lib}"
+
+# Where we send DoH queries when using kdig or curl
+: "${DOHSERVER:=one.one.one.one}"
+: "${DOHPATH:=dns-query}"
+
+# Whether to send mail when bad things happen (mostly for cronjob)
+: "${DOMAIL:=no}"
+
+# Misc vars and functions
+
+DEFPORT=443
+
+function whenisitagain()
+{
+ /bin/date -u +%Y%m%d-%H%M%S
+}
+
+function fileage()
+{
+ echo $(($(date +%s) - $(date +%s -r "$1")))
+}
+
+function hostport2host()
+{
+ case $1 in
+ *:*) host=${1%:*} port=${1##*:};;
+ *) host=$1 port=$DEFPORT;;
+ esac
+ echo "$host"
+}
+
+function hostport2port()
+{
+ case $1 in
+ *:*) host=${1%:*} port=${1##*:};;
+ *) host=$1 port=$DEFPORT;;
+ esac
+ echo "$port"
+}
+
+function cli_test()
+{
+ # 1st param is target URL
+ turl=$1
+ # 2nd param is 0 if we expect curl to not work or 1 if we expect it
+ # to have worked
+ curl_winorlose=$2
+ # 3rd param is 0 if we expect ECH to not work or 1 if we expect it
+ # to have worked
+ ech_winorlose=$3
+ # remaining params are passed to command line
+ # echparms=(${@:4})
+ IFS=" " read -r -a echparms <<< "${@:4}"
+
+ TMPF=$(mktemp)
+ cmd="timeout $tout $CURL ${CURL_PARAMS[*]} ${echparms[*]} $turl >$TMPF 2>&1"
+ echo "cli_test: $cmd " >> "$logfile"
+ timeout "$tout" "$CURL" "${CURL_PARAMS[@]}" "${echparms[@]}" "$turl" >"$TMPF" 2>&1
+ eres=$?
+ if [[ "$eres" == "124" ]]
+ then
+ allgood="no"
+ echo "cli_test: Timeout running $cmd"
+ cat "$TMPF" >> "$logfile"
+ echo "cli_test: Timeout running $cmd" >> "$logfile"
+ fi
+ if [[ "$eres" != "0" && "$curl_winorlose" == "1" ]]
+ then
+ allgood="no"
+ echo "cli_test: curl failure running $cmd"
+ cat "$TMPF" >> "$logfile"
+ echo "cli_test: curl failure running $cmd" >> "$logfile"
+ fi
+ ech_success=$(grep -c "ECH: result: status is succeeded" "$TMPF")
+ if [[ "$ech_success" == "$ech_winorlose" ]]
+ then
+ echo "cli_test ok for ${echparms[*]}"
+ else
+ allgood="no"
+ echo "cli_test: ECH failure running $cmd"
+ cat "$TMPF" >> "$logfile"
+ echo "cli_test: ECH failure running $cmd" >> "$logfile"
+ fi
+ rm -f "$TMPF"
+}
+
+function get_ech_configlist()
+{
+ domain=$1
+ ecl=$(dig +short https "$domain" | grep "ech=" | sed -e 's/^.*ech=//' | sed -e 's/ .*//')
+ echo "$ecl"
+}
+
+# start of main script
+
+# start by assuming we have nothing we need...
+have_ossl="no"
+have_wolf="no"
+have_bssl="no"
+using_ossl="no"
+using_wolf="no"
+using_bssl="no"
+have_curl="no"
+have_dig="no"
+have_kdig="no"
+have_presout="no"
+have_portsblocked="no"
+
+# setup logging
+NOW=$(whenisitagain)
+BINNAME=$(basename "$0" .sh)
+if [ ! -d "$LTOP" ]
+then
+ mkdir -p "$LTOP"
+fi
+if [ ! -d "$LTOP" ]
+then
+ echo "Can't see $LTOP for logs - exiting"
+ exit 1
+fi
+logfile=$LTOP/${BINNAME}_$NOW.log
+
+echo "-----" > "$logfile"
+echo "Running $0 at $NOW" >> "$logfile"
+echo "Running $0 at $NOW"
+
+# check we have the binaries needed and which TLS library we'll be using
+if [ -f "$OSSL"/libssl.so ]
+then
+ have_ossl="yes"
+fi
+if [ -f "$WSSL"/libwolfssl.so ]
+then
+ have_wolf="yes"
+fi
+if [ -f "$BSSL"/libssl.so ]
+then
+ have_bssl="yes"
+fi
+CURL="$CTOP/src/curl"
+CURL_PARAMS=(-vvv --doh-url https://one.one.one.one/dns-query)
+if [ -f "$CTOP"/src/curl ]
+then
+ have_curl="yes"
+fi
+ossl_cnt=$(LD_LIBRARY_PATH=$OSSL $CURL "${CURL_PARAMS[@]}" -V 2> /dev/null | grep -c OpenSSL)
+if ((ossl_cnt == 1))
+then
+ using_ossl="yes"
+ # setup access to our .so
+ export LD_LIBRARY_PATH=$OSSL
+fi
+bssl_cnt=$(LD_LIBRARY_PATH=$BSSL $CURL "${CURL_PARAMS[@]}" -V 2> /dev/null | grep -c BoringSSL)
+if ((bssl_cnt == 1))
+then
+ using_bssl="yes"
+ # setup access to our .so
+ export LD_LIBRARY_PATH=$BSSL
+fi
+wolf_cnt=$($CURL "${CURL_PARAMS[@]}" -V 2> /dev/null | grep -c wolfSSL)
+if ((wolf_cnt == 1))
+then
+ using_wolf="yes"
+ # for some reason curl+wolfSSL dislikes certs that are ok
+ # for browsers, so we'll test using "insecure" mode (-k)
+ # but that's ok here as we're only interested in ECH testing
+ CURL_PARAMS+=(-k)
+fi
+# check if we have dig and it knows https or not
+digcmd="dig +short"
+wdig=$(type -p dig)
+if [[ "$wdig" != "" ]]
+then
+ have_dig="yes"
+fi
+wkdig=$(type -p kdig)
+if [[ "$wkdig" != "" ]]
+then
+ have_kdig="yes"
+ digcmd="kdig @$DOHSERVER +https +short"
+fi
+# see if our dig version knows HTTPS
+dout=$($digcmd https defo.ie)
+if [[ $dout != "1 . "* ]]
+then
+ dout=$($digcmd -t TYPE65 defo.ie)
+ if [[ $dout == "1 . "* ]]
+ then
+ # we're good
+ have_presout="yes"
+ fi
+else
+ have_presout="yes"
+fi
+
+# Check if ports other than 443 are blocked from this
+# vantage point (I run tests in a n/w where that's
+# sadly true sometimes;-)
+# echo "Checking if ports other than 443 are maybe blocked"
+not443testurl="https://draft-13.esni.defo.ie:9413/"
+timeout "$tout" "$CURL" "${CURL_PARAMS[@]}" "$not443testurl" >/dev/null 2>&1
+eres=$?
+if [[ "$eres" == "124" ]]
+then
+ echo "Timeout running curl for $not443testurl" >> "$logfile"
+ echo "Timeout running curl for $not443testurl"
+ have_portsblocked="yes"
+fi
+
+{
+ echo "have_ossl: $have_ossl"
+ echo "have_wolf: $have_wolf"
+ echo "have_bssl: $have_bssl"
+ echo "using_ossl: $using_ossl"
+ echo "using_wolf: $using_wolf"
+ echo "using_bssl: $using_bssl"
+ echo "have_curl: $have_curl"
+ echo "have_dig: $have_dig"
+ echo "have_kdig: $have_kdig"
+ echo "have_presout: $have_presout"
+ echo "have_portsblocked: $have_portsblocked"
+} >> "$logfile"
+
+echo "curl: have $have_curl, cURL command: |$CURL ${CURL_PARAMS[*]}|"
+echo "ossl: have: $have_ossl, using: $using_ossl"
+echo "wolf: have: $have_wolf, using: $using_wolf"
+echo "bssl: have: $have_bssl, using: $using_bssl"
+echo "dig: $have_dig, kdig: $have_kdig, HTTPS pres format: $have_presout"
+echo "dig command: |$digcmd|"
+echo "ports != 443 blocked: $have_portsblocked"
+
+if [[ "$have_curl" == "no" ]]
+then
+ echo "Can't proceed without curl - exiting"
+ exit 32
+fi
+
+allgood="yes"
+
+skip="false"
+
+if [[ "$skip" != "true" ]]
+then
+
+# basic ECH good/bad
+for targ in "${!ech_targets[@]}"
+do
+ if [[ "$using_wolf" == "yes" ]]
+ then
+ case $targ in
+ "draft-13.esni.defo.ie:8414" | "tls-ech.dev" | \
+ "crypto.cloudflare.com" | "epochbelt.com")
+ echo "Skipping $targ 'cause wolf"; continue;;
+ *)
+ ;;
+ esac
+ fi
+ host=$(hostport2host "$targ")
+ port=$(hostport2port "$targ")
+ if [[ "$port" != "443" && "$have_portsblocked" == "yes" ]]
+ then
+ echo "Skipping $targ as ports != 443 seem blocked"
+ continue
+ fi
+ path=${ech_targets[$targ]}
+ turl="https://$host:$port/$path"
+ echo "ECH check for $turl"
+ {
+ echo ""
+ echo "ECH check for $turl"
+ } >> "$logfile"
+ timeout "$tout" "$CURL" "${CURL_PARAMS[@]}" --ech hard "$turl" >> "$logfile" 2>&1
+ eres=$?
+ if [[ "$eres" == "124" ]]
+ then
+ allgood="no"
+ {
+ echo "Timeout for $turl"
+ echo -e "\tTimeout for $turl"
+ echo "Timeout running curl for $host:$port/$path"
+ } >> "$logfile"
+ fi
+ if [[ "$eres" != "0" ]]
+ then
+ allgood="no"
+ echo "Error ($eres) for $turl" >> "$logfile"
+ echo -e "\tError ($eres) for $turl"
+ fi
+ echo "" >> "$logfile"
+done
+
+# check if public_name override works (OpenSSL only)
+if [[ "$using_ossl" == "yes" ]]
+then
+ for targ in "${!ech_targets[@]}"
+ do
+ host=$(hostport2host "$targ")
+ port=$(hostport2port "$targ")
+ if [[ "$port" != "443" && "$have_portsblocked" == "yes" ]]
+ then
+ echo "Skipping $targ as ports != 443 seem blocked"
+ continue
+ fi
+ path=${ech_targets[$targ]}
+ turl="https://$host:$port/$path"
+ echo "PN override check for $turl"
+ {
+ echo ""
+ echo "PN override check for $turl"
+ } >> "$logfile"
+ timeout "$tout" "$CURL" "${CURL_PARAMS[@]}" --ech pn:override --ech hard "$turl" >> "$logfile" 2>&1
+ eres=$?
+ if [[ "$eres" == "124" ]]
+ then
+ allgood="no"
+ {
+ echo "Timeout for $turl"
+ echo -e "\tTimeout for $turl"
+ echo "Timeout running curl for $host:$port/$path"
+ } >> "$logfile"
+ fi
+ if [[ "$eres" != "0" ]]
+ then
+ allgood="no"
+ echo "PN override Error ($eres) for $turl" >> "$logfile"
+ echo -e "\tPN override Error ($eres) for $turl"
+ fi
+ echo "" >> "$logfile"
+ done
+fi
+
+for targ in "${!httpsrr_targets[@]}"
+do
+ host=$(hostport2host "$targ")
+ port=$(hostport2port "$targ")
+ if [[ "$port" != "443" && "$have_portsblocked" == "yes" ]]
+ then
+ echo "Skipping $targ as ports != 443 seem blocked"
+ continue
+ fi
+ path=${httpsrr_targets[$targ]}
+ turl="https://$host:$port/$path"
+ echo "HTTPS RR but no ECHConfig check for $turl"
+ {
+ echo ""
+ echo "HTTPS RR but no ECHConfig check for $turl"
+ } >> "$logfile"
+ timeout "$tout" "$CURL" "${CURL_PARAMS[@]}" --ech true "$turl" >> "$logfile" 2>&1
+ eres=$?
+ if [[ "$eres" == "124" ]]
+ then
+ allgood="no"
+ {
+ echo "Timeout for $turl"
+ echo -e "\tTimeout for $turl"
+ echo "Timeout running curl for $host:$port/$path"
+ } >> "$logfile"
+ fi
+ if [[ "$eres" != "0" ]]
+ then
+ allgood="no"
+ echo "Error ($eres) for $turl" >> "$logfile"
+ echo -e "\tError ($eres) for $turl"
+ fi
+ echo "" >> "$logfile"
+done
+
+for targ in "${!neither_targets[@]}"
+do
+ host=$(hostport2host "$targ")
+ port=$(hostport2port "$targ")
+ if [[ "$port" != "443" && "$have_portsblocked" == "yes" ]]
+ then
+ echo "Skipping $targ as ports != 443 seem blocked"
+ continue
+ fi
+ path=${neither_targets[$targ]}
+ turl="https://$host:$port/$path"
+ echo "Neither HTTPS nor ECHConfig check for $turl"
+ {
+ echo ""
+ echo "Neither HTTPS nor ECHConfig check for $turl"
+ } >> "$logfile"
+ timeout "$tout" "$CURL" "${CURL_PARAMS[@]}" --ech true "$turl" >> "$logfile" 2>&1
+ eres=$?
+ if [[ "$eres" == "124" ]]
+ then
+ allgood="no"
+ {
+ echo "Timeout for $turl"
+ echo -e "\tTimeout for $turl"
+ echo "Timeout running curl for $host:$port/$path"
+ } >> "$logfile"
+ fi
+ if [[ "$eres" != "0" ]]
+ then
+ allgood="no"
+ echo "Error ($eres) for $turl" >> "$logfile"
+ echo -e "\tError ($eres) for $turl"
+ fi
+ echo "" >> "$logfile"
+done
+
+
+# Check various command line options, if we're good so far
+if [[ "$using_ossl" == "yes" && "$allgood" == "yes" ]]
+then
+ # use this test URL as it'll tell us if things worked
+ turl="https://defo.ie/ech-check.php"
+ echo "cli_test with $turl"
+ echo "cli_test with $turl" >> "$logfile"
+ cli_test "$turl" 1 1 --ech true
+ cli_test "$turl" 1 0 --ech false
+ cli_test "$turl" 1 1 --ech false --ech true
+ cli_test "$turl" 1 1 --ech false --ech true --ech pn:foobar
+ cli_test "$turl" 1 1 --ech false --ech pn:foobar --ech true
+ echconfiglist=$(get_ech_configlist defo.ie)
+ cli_test "$turl" 1 1 --ech ecl:"$echconfiglist"
+ cli_test "$turl" 1 0 --ech ecl:
+fi
+
+fi # skip
+
+# Check combinations of command line options, if we're good so far
+# Most of this only works for openssl, which is ok, as we're checking
+# the argument handling here, not the ECH protocol
+if [[ "$using_ossl" == "yes" && "$allgood" == "yes" ]]
+then
+ # ech can be hard, true, grease or false
+ # ecl:ecl can be correct, incorrect or missing
+ # ech:pn can be correct, incorrect or missing
+ # in all cases the "last" argument provided should "win"
+ # but only one of hard, true, grease or false will apply
+ turl="https://defo.ie/ech-check.php"
+ echconfiglist=$(get_ech_configlist defo.ie)
+ goodecl=$echconfiglist
+ echconfiglist=$(get_ech_configlist hidden.hoba.ie)
+ badecl=$echconfiglist
+ goodpn="cover.defo.ie"
+ badpn="hoba.ie"
+ echo "more cli_test with $turl"
+ echo "more cli_test with $turl" >> "$logfile"
+
+ # The combinatorics here are handled via the tests/ech_combos.py script
+ # which produces all the relevant combinations or inputs and orders
+ # thereof. We have to manually assess whether or not ECH is expected to
+ # work for each case.
+ cli_test "$turl" 0 0
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$badecl" --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$badecl" --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech hard
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$badecl" --ech hard --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$badecl" --ech hard --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech hard --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech hard --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$badecl" --ech hard --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$badecl" --ech hard --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech hard --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$badecl" --ech pn:$badpn --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$badecl" --ech pn:$badpn --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech hard
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$badecl" --ech pn:$badpn --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$badecl" --ech pn:$badpn --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" - 0 --ech ecl:"$badecl" --ech pn:$badpn --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$badecl" --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$badecl" --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech false
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech false --ech ecl:"$badecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech false --ech ecl:"$badecl" --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech false --ech ecl:"$badecl" --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech hard
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech ecl:"$badecl" --ech hard --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech ecl:"$badecl" --ech hard --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech hard --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech hard --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech ecl:"$badecl" --ech hard --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech ecl:"$badecl" --ech hard --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech hard --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech hard
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech false --ech ecl:"$badecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech ecl:"$badecl" --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech ecl:"$badecl" --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech false --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech false --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech hard
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech hard --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech hard --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech hard --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech hard --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech hard --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech hard --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech hard --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech false --ech pn:$badpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech false --ech pn:$badpn --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech false --ech pn:$badpn --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech pn:$badpn --ech hard
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech pn:$badpn --ech hard --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech pn:$badpn --ech hard --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech pn:$badpn --ech hard --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech pn:$badpn --ech hard --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech pn:$badpn --ech hard --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech pn:$badpn --ech hard --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech pn:$badpn --ech hard --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech false --ech pn:$badpn --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech pn:$badpn --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech pn:$badpn --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech pn:$badpn --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech pn:$badpn --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech false --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech false --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech hard
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech hard --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech hard --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech hard --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech hard --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech hard --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech hard --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech hard --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech pn:$badpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech pn:$badpn --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech pn:$badpn --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech pn:$badpn --ech hard
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech pn:$badpn --ech hard --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech pn:$badpn --ech hard --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech pn:$badpn --ech hard --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech pn:$badpn --ech hard --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech pn:$badpn --ech hard --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech pn:$badpn --ech hard --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech pn:$badpn --ech hard --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech pn:$badpn --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech pn:$badpn --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech pn:$badpn --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech pn:$badpn --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech pn:$badpn --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 0 --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 1 1 --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+
+ # a target URL that doesn't support ECH
+ turl="https://tcd.ie"
+ echo "cli_test with $turl"
+ echo "cli_test with $turl" >> "$logfile"
+ # the params below don't matter much here as we'll fail anyway
+ echconfiglist=$(get_ech_configlist defo.ie)
+ goodecl=$echconfiglist
+ badecl="$goodecl"
+ goodpn="tcd.ie"
+ badpn="tcd.ie"
+ cli_test "$turl" 1 0
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech hard
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech hard --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech hard --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech hard --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech hard --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech hard --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech hard --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech hard --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech hard
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$badpn --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$badecl" --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech hard
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech hard --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech hard --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech hard --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech hard --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech hard --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech hard --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech hard --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech hard
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech hard --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$badpn --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$badecl" --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech hard
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech hard --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech hard --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech hard --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech hard --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech hard --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech hard --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech hard --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech pn:$badpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech pn:$badpn --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech pn:$badpn --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech pn:$badpn --ech hard
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech pn:$badpn --ech hard --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech pn:$badpn --ech hard --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech pn:$badpn --ech hard --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech pn:$badpn --ech hard --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech pn:$badpn --ech hard --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech pn:$badpn --ech hard --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech pn:$badpn --ech hard --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech pn:$badpn --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech pn:$badpn --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech pn:$badpn --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech pn:$badpn --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech pn:$badpn --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech false --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech hard
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech hard --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech hard --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech hard --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech hard --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech hard --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech hard --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech hard --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech pn:$badpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech pn:$badpn --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech pn:$badpn --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech pn:$badpn --ech hard
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech pn:$badpn --ech hard --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech pn:$badpn --ech hard --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech pn:$badpn --ech hard --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech pn:$badpn --ech hard --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech pn:$badpn --ech hard --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech pn:$badpn --ech hard --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech pn:$badpn --ech hard --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech pn:$badpn --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech pn:$badpn --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech pn:$badpn --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech pn:$badpn --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech pn:$badpn --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech true
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech true --ech ecl:"$goodecl"
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech true --ech ecl:"$goodecl" --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+ cli_test "$turl" 0 0 --ech true --ech pn:$goodpn
+ if [[ "$allgood" != "yes" ]]; then echo $LINENO; fi
+fi
+
+
+END=$(whenisitagain)
+echo "Finished $0 at $END" >> "$logfile"
+echo "-----" >> "$logfile"
+
+if [[ "$allgood" == "yes" ]]
+then
+ echo "Finished $0 at $END"
+ echo "All good, log in $logfile"
+ exit 0
+else
+ echo "Finished $0 at $END"
+ echo "NOT all good, log in $logfile"
+fi
+
+# send a mail to root (will be fwd'd) but just once every 24 hours
+# 'cause we only really need "new" news
+itsnews="yes"
+age_of_news=0
+if [ -f "$LTOP"/bad_runs ]
+then
+ age_of_news=$(fileage "$LTOP"/bad_runs)
+ # only consider news "new" if we haven't mailed today
+ if ((age_of_news < 24*3600))
+ then
+ itsnews="no"
+ fi
+fi
+if [[ "$DOMAIL" == "yes" && "$itsnews" == "yes" ]]
+then
+ echo "ECH badness at $NOW" | mail -s "ECH badness at $NOW" root
+fi
+# add to list of bad runs (updating file age)
+echo "ECH badness at $NOW" >>"$LTOP"/bad_runs
+exit 2
+