]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Detail client closures of CONNECT tunnels during TLS handshake (#691)
authorChristos Tsantilas <christos@chtsanti.net>
Thu, 10 Dec 2020 20:12:45 +0000 (20:12 +0000)
committerSquid Anubis <squid-anubis@squid-cache.org>
Wed, 23 Dec 2020 16:34:46 +0000 (16:34 +0000)
... and improve detailing of other errors.

Many admins cannot triage TLS client failures, and even Squid developers
often cannot diagnose TLS problems without requiring detailed debugging
logs of failing transactions. The problem is especially bad for busy
proxies where debugging individual transactions is often impractical.

We enhance existing error detailing code so that more information is
logged via the existing %err_code/%err_detail logformat codes.
Propagating low-level error details required significant enhancements
and refactoring. We also built initial scaffolding for better error
detailing by GnuTLS-driven code and documented several key
error-handling APIs, exposing a few out-of-scope problems.

Also checkLogging() once, after consuming unparsed input attributed to a
transaction: Due to fake CONNECT requests, from-client read errors, and
possibly other complications, we may have a transaction that did not
consume every input byte available to it. That transaction is still
responsible for reporting those unparsed bytes (e.g., by logging the
number of bytes read on a connection and the number of parsed bytes).

Also fixed passing wrong (errno vs. size) or stale (requested vs. read)
I/O size to connFinishedWithConn(); now shouldCloseOnEof(). The bad
value was "correct" (i.e. zero) in many cases, obscuring the bug.

This is a Measurement Factory project

88 files changed:
configure.ac
doc/debug-sections.txt
errors/templates/error-details.txt
scripts/calc-must-ids.sh
scripts/source-maintenance.sh
src/AccessLogEntry.cc
src/AccessLogEntry.h
src/FwdState.cc
src/FwdState.h
src/HttpRequest.cc
src/HttpRequest.h
src/LogTags.cc
src/LogTags.h
src/Makefile.am
src/Pipeline.cc
src/Pipeline.h
src/acl/AclDenyInfoList.h
src/acl/FilledChecklist.h
src/acl/Gadgets.h
src/acl/SquidError.cc
src/acl/SquidError.h
src/acl/SquidErrorData.cc
src/acl/SquidErrorData.h
src/adaptation/icap/ModXact.cc
src/adaptation/icap/ModXact.h
src/adaptation/icap/Xaction.cc
src/adaptation/icap/Xaction.h
src/base/TextException.cc
src/base/TextException.h
src/client_side.cc
src/client_side.h
src/client_side_request.cc
src/client_side_request.h
src/clients/Client.cc
src/clients/FtpClient.cc
src/clients/FtpClient.h
src/clients/FtpGateway.cc
src/clients/FtpRelay.cc
src/comm/Read.cc
src/err_detail_type.h [deleted file]
src/error/Detail.cc [new file with mode: 0644]
src/error/Detail.h [new file with mode: 0644]
src/error/Error.cc [new file with mode: 0644]
src/error/Error.h [new file with mode: 0644]
src/error/ExceptionErrorDetail.h [new file with mode: 0644]
src/error/Makefile.am [new file with mode: 0644]
src/error/SysErrorDetail.h [new file with mode: 0644]
src/error/forward.h [moved from src/err_type.h with 84% similarity]
src/errorpage.cc
src/errorpage.h
src/esi/Context.h
src/format/Format.cc
src/http.cc
src/http/Stream.cc
src/http/Stream.h
src/log/access_log.cc
src/log/forward.h
src/mk-string-arrays.awk
src/security/ErrorDetail.cc [new file with mode: 0644]
src/security/ErrorDetail.h [new file with mode: 0644]
src/security/Io.cc [new file with mode: 0644]
src/security/Io.h [new file with mode: 0644]
src/security/KeyData.cc
src/security/LockingPointer.h
src/security/Makefile.am
src/security/PeerConnector.cc
src/security/PeerConnector.h
src/security/PeerOptions.cc
src/security/ServerOptions.cc
src/security/Session.cc
src/security/forward.h
src/servers/FtpServer.cc
src/servers/Server.cc
src/servers/Server.h
src/ssl/ErrorDetail.cc
src/ssl/ErrorDetail.h
src/ssl/ErrorDetailManager.h
src/ssl/PeekingPeerConnector.cc
src/ssl/PeekingPeerConnector.h
src/ssl/cert_validate_message.cc
src/ssl/cert_validate_message.h
src/ssl/support.cc
src/ssl/support.h
src/tests/stub_HttpRequest.cc
src/tests/stub_liberror.cc [new file with mode: 0644]
src/tests/stub_libhttp.cc
src/tests/stub_libsecurity.cc
src/tests/stub_libsslsquid.cc

index 5409704c6afb3ea16f9d6dfdacc02a084dc08366..2396cd74ba92e09c587fdc63114b5e22f9482011 100644 (file)
@@ -3742,6 +3742,7 @@ AC_CONFIG_FILES([
        src/DiskIO/DiskThreads/Makefile
        src/DiskIO/IpcIo/Makefile
        src/DiskIO/Mmapped/Makefile
+       src/error/Makefile
        src/esi/Makefile
        src/eui/Makefile
        src/format/Makefile
index e51cfa3978015d4fa9dc66b173528a46399322c6..4824d165ca67fc2f61edcd793d0d07a15b5a50ca 100644 (file)
@@ -18,6 +18,7 @@ section 02    Unlink Daemon
 section 03    Configuration File Parsing
 section 03    Configuration Settings
 section 04    Error Generation
+section 04    Error Management
 section 05    Comm
 section 05    Disk I/O pipe manager
 section 05    Listener Socket Handler
@@ -139,6 +140,7 @@ section 81    Store HEAP Removal Policies
 section 82    External ACL
 section 83    SSL accelerator support
 section 83    SSL-Bump Server/Peer negotiation
+section 83    TLS I/O
 section 83    TLS Server/Peer negotiation
 section 83    TLS session management
 section 84    Helper process maintenance
index ef46b0928e4a9fa907ee1a8988c5cb76a2ae99a3..881add9909562c3b61f339e7eb340ee24b10bfc6 100644 (file)
@@ -2,9 +2,13 @@ name: SQUID_X509_V_ERR_INFINITE_VALIDATION
 detail: "%ssl_error_descr: %ssl_subject"
 descr: "Cert validation infinite loop detected"
 
-name: SQUID_ERR_SSL_HANDSHAKE
+name: SQUID_TLS_ERR_ACCEPT
 detail: "%ssl_error_descr: %ssl_lib_error"
-descr: "Handshake with SSL server failed"
+descr: "Failed to accept a secure connection"
+
+name: SQUID_TLS_ERR_CONNECT
+detail: "%ssl_error_descr: %ssl_lib_error"
+descr: "Failed to establish a secure connection"
 
 name: SQUID_X509_V_ERR_DOMAIN_MISMATCH
 detail: "%ssl_error_descr: %ssl_subject"
index 80dbf5a0a1ec0c3ab6b6e26071fa209a80eaa73e..c39558812c2e78f8ce7d7d349f0f10d9970a584d 100755 (executable)
@@ -23,7 +23,7 @@ if test -z "$1"; then
         xargs `dirname $0`/calc-must-ids.pl
 else
     find . -name "*.cc" -o -name "*.h" -o -name "*.cci" | \
-        xargs `dirname $0`/calc-must-ids.pl | grep ": $1 "
+        xargs `dirname $0`/calc-must-ids.pl | grep -Ei ": (0x)?$1 "
 fi
 
 
index 47e25f892fb0eabdfb3732c32c2a68a99f63a500..163a827c484e2bdab4b55768977561165d2fd1f7 100755 (executable)
@@ -150,6 +150,31 @@ applyPluginsTo ()
         done
 }
 
+# succeeds if all MakeNamedErrorDetail() names are unique
+checkMakeNamedErrorDetails ()
+{
+    problems=1 # assume there are problems until proven otherwise
+
+    options='-h --only-matching --extended-regexp'
+    git grep $options 'MakeNamedErrorDetail[(]".*?"[)]' src |
+        sort |
+        uniq --count > \
+        MakeNamedErrorDetail.tmp
+
+    if grep --quiet --word-regexp 1 MakeNamedErrorDetail.tmp; then
+        if grep --invert-match --word-regexp 1 MakeNamedErrorDetail.tmp; then
+            echo "ERROR: Duplicated MakeNamedErrorDetail names (see above)."
+        else
+            problems=0
+        fi
+    else
+        echo "ERROR: Cannot find or process MakeNamedErrorDetail calls."
+    fi
+
+    rm MakeNamedErrorDetail.tmp # ignore (unexpected) cleanup failures
+    return $problems
+}
+
 srcFormat ()
 {
 #
@@ -350,6 +375,8 @@ printAmFile STUB_SOURCE "src/" "tests/stub_*.cc" > src/tests/Stub.am
 # Build the GPERF generated content
 make -C src/http gperf-files
 
+run_ checkMakeNamedErrorDetails || exit 1
+
 # Run formatting
 echo "" >doc/debug-sections.tmp
 srcFormat || exit 1
index 9cb39ea6b8f054475ff055fe49ebb5ba3f2fa550..9d9bf06e547ae612f60f7edc2ff3cc686867562b 100644 (file)
@@ -175,6 +175,27 @@ AccessLogEntry::effectiveVirginUrl() const
     return nullptr;
 }
 
+const Error *
+AccessLogEntry::error() const
+{
+    // the order ensures that the first-imported error is returned
+    if (error_) // updateError() was called before importing the request
+        return &error_;
+    if (request && request->error) // request was imported before updateError()
+        return &request->error;
+    return nullptr; // we imported no errors and no requests
+}
+
+void
+AccessLogEntry::updateError(const Error &err)
+{
+    // the order ensures that error() returns the first-imported error
+    if (request)
+        request->error.update(err);
+    else
+        error_.update(err);
+}
+
 void
 AccessLogEntry::packReplyHeaders(MemBuf &mb) const
 {
index 28fe63c67a5417a41536fa68886aa7626fcc03f1..65d1f61e138381ae5b95c4cf310e49c878b52933 100644 (file)
@@ -12,6 +12,7 @@
 #include "anyp/PortCfg.h"
 #include "base/CodeContext.h"
 #include "comm/Connection.h"
+#include "error/Error.h"
 #include "HierarchyLogEntry.h"
 #include "http/ProtocolVersion.h"
 #include "http/RequestMethod.h"
@@ -255,7 +256,17 @@ public:
             virginUrlForMissingRequest_ = vu;
     }
 
+    /// \returns stored transaction error information (or nil)
+    const Error *error() const;
+
+    /// sets (or updates the already stored) transaction error as needed
+    void updateError(const Error &);
+
 private:
+    /// transaction problem
+    /// if set, overrides (and should eventually replace) request->error
+    Error error_;
+
     /// Client URI (or equivalent) for effectiveVirginUrl() when HttpRequest is
     /// missing. This member is ignored unless the request member is nil.
     SBuf virginUrlForMissingRequest_;
index 5be266720f2ff29787b1e2007edbca1ae36e7ee6..fda7ff494f5467da1b780329c616ae8800ad6df9 100644 (file)
@@ -59,7 +59,6 @@
 #if USE_OPENSSL
 #include "ssl/cert_validate_message.h"
 #include "ssl/Config.h"
-#include "ssl/ErrorDetail.h"
 #include "ssl/helper.h"
 #include "ssl/ServerBump.h"
 #include "ssl/support.h"
index dafece93826b5de0a63e3607994ac4d132fc7d84..a23d0bf80cd0e9faccfb2d67bb98fc8744821ba9 100644 (file)
@@ -16,7 +16,7 @@
 #include "comm.h"
 #include "comm/Connection.h"
 #include "comm/ConnOpener.h"
-#include "err_type.h"
+#include "error/forward.h"
 #include "fde.h"
 #include "http/StatusCode.h"
 #include "ip/Address.h"
@@ -41,14 +41,6 @@ class HappyConnOpener;
 typedef CbcPointer<HappyConnOpener> HappyConnOpenerPointer;
 class HappyConnOpenerAnswer;
 
-#if USE_OPENSSL
-namespace Ssl
-{
-class ErrorDetail;
-class CertValidationResponse;
-};
-#endif
-
 /// Sets initial TOS value and Netfilter for the future outgoing connection.
 /// Updates the given Connection object, not the future transport connection.
 void GetMarkingsToServer(HttpRequest * request, Comm::Connection &conn);
index 6cfc7859a85380df819119dabff201a26632aa9c..3ad30666c9895ea51e0ce78901e99a44aed9da1e 100644 (file)
@@ -17,7 +17,7 @@
 #include "client_side_request.h"
 #include "dns/LookupDetails.h"
 #include "Downloader.h"
-#include "err_detail_type.h"
+#include "error/Detail.h"
 #include "globals.h"
 #include "gopher.h"
 #include "http.h"
@@ -91,8 +91,7 @@ HttpRequest::init()
     body_pipe = NULL;
     // hier
     dnsWait = -1;
-    errType = ERR_NONE;
-    errDetail = ERR_DETAIL_NONE;
+    error.clear();
     peer_login = NULL;      // not allocated/deallocated by this class
     peer_domain = NULL;     // not allocated/deallocated by this class
     peer_host = NULL;
@@ -193,7 +192,7 @@ HttpRequest::clone() const
     copy->imslen = imslen;
     copy->hier = hier; // Is it safe to copy? Should we?
 
-    copy->errType = errType;
+    copy->error = error;
 
     // XXX: what to do with copy->peer_login?
 
@@ -238,8 +237,7 @@ HttpRequest::inheritProperties(const Http::Message *aMsg)
     // may eventually need cloneNullAdaptationImmune() for that.
     flags = aReq->flags.cloneAdaptationImmune();
 
-    errType = aReq->errType;
-    errDetail = aReq->errDetail;
+    error = aReq->error;
 #if USE_AUTH
     auth_user_request = aReq->auth_user_request;
     extacl_user = aReq->extacl_user;
@@ -268,7 +266,7 @@ HttpRequest::inheritProperties(const Http::Message *aMsg)
  * NP: Other errors are left for detection later in the parse.
  */
 bool
-HttpRequest::sanityCheckStartLine(const char *buf, const size_t hdr_len, Http::StatusCode *error)
+HttpRequest::sanityCheckStartLine(const char *buf, const size_t hdr_len, Http::StatusCode *scode)
 {
     // content is long enough to possibly hold a reply
     // 2 being magic size of a 1-byte request method plus space delimiter
@@ -276,7 +274,7 @@ HttpRequest::sanityCheckStartLine(const char *buf, const size_t hdr_len, Http::S
         // this is only a real error if the headers apparently complete.
         if (hdr_len > 0) {
             debugs(58, 3, HERE << "Too large request header (" << hdr_len << " bytes)");
-            *error = Http::scInvalidHeader;
+            *scode = Http::scInvalidHeader;
         }
         return false;
     }
@@ -286,7 +284,7 @@ HttpRequest::sanityCheckStartLine(const char *buf, const size_t hdr_len, Http::S
     m.HttpRequestMethodXXX(buf);
     if (m == Http::METHOD_NONE) {
         debugs(73, 3, "HttpRequest::sanityCheckStartLine: did not find HTTP request method");
-        *error = Http::scInvalidHeader;
+        *scode = Http::scInvalidHeader;
         return false;
     }
 
@@ -464,26 +462,11 @@ HttpRequest::prepForDirect()
     debugs(11, 4, this);
 }
 
-void
-HttpRequest::detailError(err_type aType, int aDetail)
-{
-    if (errType || errDetail)
-        debugs(11, 5, HERE << "old error details: " << errType << '/' << errDetail);
-    debugs(11, 5, HERE << "current error details: " << aType << '/' << aDetail);
-    // checking type and detail separately may cause inconsistency, but
-    // may result in more details available if they only become available later
-    if (!errType)
-        errType = aType;
-    if (!errDetail)
-        errDetail = aDetail;
-}
-
 void
 HttpRequest::clearError()
 {
-    debugs(11, 7, HERE << "old error details: " << errType << '/' << errDetail);
-    errType = ERR_NONE;
-    errDetail = ERR_DETAIL_NONE;
+    debugs(11, 7, "old: " << error);
+    error.clear();
 }
 
 void
index 57fb1b4bea09d121c21542abb1147d61f0a5bea8..de36fdbfe5da6f175750f355b37564cd76539926 100644 (file)
@@ -12,7 +12,7 @@
 #include "anyp/Uri.h"
 #include "base/CbcPointer.h"
 #include "dns/forward.h"
-#include "err_type.h"
+#include "error/Error.h"
 #include "HierarchyLogEntry.h"
 #include "http/Message.h"
 #include "http/RequestMethod.h"
@@ -98,7 +98,7 @@ public:
     void recordLookup(const Dns::LookupDetails &detail);
 
     /// sets error detail if no earlier detail was available
-    void detailError(err_type aType, int aDetail);
+    void detailError(const err_type c, const ErrorDetail::Pointer &d) { error.update(c, d); }
     /// clear error details, useful for retries/repeats
     void clearError();
 
@@ -158,8 +158,7 @@ public:
 
     int dnsWait; ///< sum of DNS lookup delays in milliseconds, for %dt
 
-    err_type errType;
-    int errDetail; ///< errType-specific detail about the transaction error
+    Error error; ///< the first transaction problem encountered (or falsy)
 
     char *peer_login;       /* Configured peer login:password */
 
index 49d0579241b7d3c14ebade2a91dc202f4564f155..f0f808f3d39a54b061d89c43ae74460b1a94f443 100644 (file)
 #include "Debug.h"
 #include "LogTags.h"
 
+void
+LogTagsErrors::update(const LogTagsErrors &other)
+{
+    ignored = ignored || other.ignored;
+    timedout = timedout || other.timedout;
+    aborted = aborted || other.aborted;
+}
+
+/* LogTags */
+
 // old deprecated tag strings
 const char * LogTags::Str_[] = {
     "TAG_NONE",
index e5732765ffb9a97be946e49f72ff9658c3a3f1fc..5dd661cacdfe067aca31e9b5e3335f4914c059d2 100644 (file)
 
 #include "CollapsingHistory.h"
 
+/// Things that may happen to a transaction while it is being
+/// processed according to its LOG_* category. Logged as _SUFFIX(es).
+/// Unlike LOG_* categories, these flags may not be mutually exclusive.
+class LogTagsErrors
+{
+public:
+    /// Update each of this object flags to "set" if the corresponding
+    /// flag of the given object is set
+    void update(const LogTagsErrors &other);
+
+    bool ignored = false; ///< _IGNORED: the response was not used for anything
+    bool timedout = false; ///< _TIMEDOUT: terminated due to a lifetime or I/O timeout
+    bool aborted = false;  ///< _ABORTED: other abnormal termination (e.g., I/O error)
+};
+
 /** Squid transaction result code/tag set.
  *
  * These codes indicate how the request was received
@@ -65,17 +80,8 @@ public:
     /// \returns Cache-Status "hit" or "fwd=..." parameter (or nil)
     const char *cacheStatusSource() const;
 
-    /// Things that may happen to a transaction while it is being
-    /// processed according to its LOG_* category. Logged as _SUFFIX(es).
-    /// Unlike LOG_* categories, these flags may not be mutually exclusive.
-    class Errors {
-    public:
-        Errors() : ignored(false), timedout(false), aborted(false) {}
-
-        bool ignored; ///< _IGNORED: the response was not used for anything
-        bool timedout; ///< _TIMEDOUT: terminated due to a lifetime or I/O timeout
-        bool aborted;  ///< _ABORTED: other abnormal termination (e.g., I/O error)
-    } err;
+    /// various problems augmenting the primary log tag
+    LogTagsErrors err;
 
 private:
     /// list of string representations for LogTags_ot
index f6d4c568b8ed73127b9b6249ba398ab8de25cb72..04632e7936217152c2d3ca14b8f3214cf88cddac 100644 (file)
@@ -31,8 +31,8 @@ LOADABLE_MODULES_SOURCES = \
        LoadableModules.cc \
        LoadableModules.h
 
-SUBDIRS        = mem base anyp helper dns ftp parser comm eui acl format clients sbuf servers fs repl store DiskIO proxyp
-DIST_SUBDIRS = mem base anyp helper dns ftp parser comm eui acl format clients sbuf servers fs repl store DiskIO proxyp
+SUBDIRS = mem base anyp helper dns ftp parser comm error eui acl format clients sbuf servers fs repl store DiskIO proxyp
+DIST_SUBDIRS = mem base anyp helper dns ftp parser comm error eui acl format clients sbuf servers fs repl store DiskIO proxyp
 
 if ENABLE_AUTH
 SUBDIRS += auth
@@ -385,8 +385,6 @@ squid_SOURCES = \
        dlink.cc \
        dlink.h \
        enums.h \
-       err_detail_type.h \
-       err_type.h \
        errorpage.cc \
        errorpage.h \
        event.cc \
@@ -509,8 +507,6 @@ noinst_HEADERS = \
 BUILT_SOURCES = \
        cf_gen_defines.cci \
        cf_parser.cci \
-       err_detail_type.cc \
-       err_type.cc \
        globals.cc \
        hier_code.cc \
        icp_opcode.cc \
@@ -545,6 +541,7 @@ squid_LDADD = \
        anyp/libanyp.la \
        security/libsecurity.la \
        $(SSL_LIBS) \
+       error/liberror.la \
        ipc/libipc.la \
        mgr/libmgr.la \
        proxyp/libproxyp.la \
@@ -732,12 +729,6 @@ globals.cc: globals.h mk-globals-c.awk
 hier_code.cc: hier_code.h mk-string-arrays.awk
        $(AWK) -f $(srcdir)/mk-string-arrays.awk < $(srcdir)/hier_code.h > $@ || ($(RM) -f $@ && exit 1)
 
-err_type.cc: err_type.h mk-string-arrays.awk
-       $(AWK) -f $(srcdir)/mk-string-arrays.awk < $(srcdir)/err_type.h > $@ || ($(RM) -f $@ && exit 1)
-
-err_detail_type.cc: err_detail_type.h mk-string-arrays.awk
-       $(AWK) -f $(srcdir)/mk-string-arrays.awk < $(srcdir)/err_detail_type.h | sed 's/ERR_DETAIL_//' > $@ || ($(RM) -f $@ && exit 1)
-
 lookup_t.cc: lookup_t.h mk-string-arrays.awk
        $(AWK) -f $(srcdir)/mk-string-arrays.awk < $(srcdir)/lookup_t.h > $@ || ($(RM) -f $@ && exit 1)
 
@@ -1195,6 +1186,7 @@ tests_testRock_SOURCES = \
        tests/stub_ipcache.cc \
        tests/stub_libanyp.cc \
        tests/stub_libauth.cc \
+       tests/stub_liberror.cc \
        tests/stub_libeui.cc \
        tests/stub_libformat.cc \
        tests/stub_libicmp.cc \
@@ -1371,6 +1363,7 @@ tests_testUfs_SOURCES = \
        tests/stub_ipcache.cc \
        tests/stub_libanyp.cc \
        tests/stub_libauth.cc \
+       tests/stub_liberror.cc \
        tests/stub_libeui.cc \
        tests/stub_libformat.cc \
        tests/stub_libicmp.cc \
@@ -1548,6 +1541,7 @@ tests_testStore_SOURCES = \
        tests/stub_libauth.cc \
        tests/stub_libcomm.cc \
        tests/stub_libdiskio.cc \
+       tests/stub_liberror.cc \
        tests/stub_libeui.cc \
        tests/stub_libformat.cc \
        tests/stub_libsecurity.cc \
@@ -1720,6 +1714,7 @@ tests_testDiskIO_SOURCES = \
        tests/stub_libanyp.cc \
        tests/stub_libauth.cc \
        tests/stub_libauth_acls.cc \
+       tests/stub_liberror.cc \
        tests/stub_libeui.cc \
        tests/stub_libformat.cc \
        tests/stub_libicmp.cc \
@@ -1977,6 +1972,7 @@ tests_test_http_range_SOURCES = \
        ipcache.cc \
        tests/stub_libauth.cc \
        tests/stub_libdiskio.cc \
+       tests/stub_liberror.cc \
        tests/stub_libeui.cc \
        tests/stub_libmem.cc \
        tests/stub_libsecurity.cc \
@@ -2192,6 +2188,7 @@ tests_testHttpReply_SOURCES = \
        tests/stub_libanyp.cc \
        tests/stub_libauth.cc \
        tests/stub_libcomm.cc \
+       tests/stub_liberror.cc \
        tests/stub_libformat.cc \
        tests/stub_libmgr.cc \
        tests/stub_libsecurity.cc \
@@ -2366,6 +2363,7 @@ tests_testHttpRequest_SOURCES = \
        tests/stub_libauth.cc \
        tests/stub_libauth_acls.cc \
        tests/stub_libdiskio.cc \
+       tests/stub_liberror.cc \
        tests/stub_libeui.cc \
        tests/stub_libmem.cc \
        tests/stub_libsecurity.cc \
@@ -2671,6 +2669,7 @@ tests_testCacheManager_SOURCES = \
        tests/stub_libauth.cc \
        tests/stub_libauth_acls.cc \
        tests/stub_libdiskio.cc \
+       tests/stub_liberror.cc \
        tests/stub_libsecurity.cc \
        tests/stub_libstore.cc \
        tests/stub_main_cc.cc \
@@ -2992,6 +2991,7 @@ tests_testEvent_SOURCES = \
        tests/stub_libauth.cc \
        tests/stub_libauth_acls.cc \
        tests/stub_libdiskio.cc \
+       tests/stub_liberror.cc \
        tests/stub_libeui.cc \
        tests/stub_libmem.cc \
        tests/stub_libsecurity.cc \
index 2443a5028e802240d6cfec6c31782f2252d473fe..eabd65a5e00ed3b37b924274ae7974b71e7e45cb 100644 (file)
@@ -48,18 +48,6 @@ Pipeline::back() const
     return requests.back();
 }
 
-void
-Pipeline::terminateAll(int xerrno)
-{
-    while (!requests.empty()) {
-        Http::StreamPointer context = requests.front();
-        debugs(33, 3, "Pipeline " << (void*)this << " notify(" << xerrno << ") " << context);
-        context->noteIoError(xerrno);
-        context->finished();  // cleanup and self-deregister
-        assert(context != requests.front());
-    }
-}
-
 void
 Pipeline::popMe(const Http::StreamPointer &which)
 {
index 86503079b6dbace45350df8ea3502f96b70fb27c..6358cd7237fc695cf4858f8ace4afb8f6694dd81 100644 (file)
@@ -55,9 +55,6 @@ public:
     /// whether there are none or any requests currently pipelined
     bool empty() const {return requests.empty();}
 
-    /// tell everybody about the err, and abort all waiting requests
-    void terminateAll(const int xerrno);
-
     /// deregister the front request from the pipeline
     void popMe(const Http::StreamPointer &);
 
index 003030df1738af8b307b138280635c301f21ca1d..5ae5decd664344f2cb8d228fbf6687a02f6d8f57 100644 (file)
@@ -10,7 +10,7 @@
 #define SQUID_ACLDENYINFOLIST_H_
 
 #include "acl/forward.h"
-#include "err_type.h"
+#include "error/forward.h"
 #include "errorpage.h"
 #include "mem/forward.h"
 #include "sbuf/forward.h"
index 838dd72ead53bf182a9003482ce2e7682e3c3455..a4ebb2d1e450a1bf41cb2f9033a9f649d5028ab3 100644 (file)
@@ -13,7 +13,7 @@
 #include "acl/Checklist.h"
 #include "acl/forward.h"
 #include "base/CbcPointer.h"
-#include "err_type.h"
+#include "error/forward.h"
 #include "ip/Address.h"
 #if USE_AUTH
 #include "auth/UserRequest.h"
index f85628a21d9f6b7333b955ad43e05e0d31bad297..c6e6cae8a1dd18625feb42dbd77fecc97f010b77 100644 (file)
@@ -10,7 +10,7 @@
 #define SQUID_ACL_GADGETS_H
 
 #include "acl/forward.h"
-#include "err_type.h"
+#include "error/forward.h"
 
 #include <sstream>
 
index 66169cebf6a53b269d11316c2abf64f964a4445b..3ec3cdffb7a5ac019b9a525a1643d39aeb1598a7 100644 (file)
@@ -17,7 +17,7 @@ ACLSquidErrorStrategy::match (ACLData<MatchType> * &data, ACLFilledChecklist *ch
     if (checklist->requestErrorType != ERR_MAX)
         return data->match(checklist->requestErrorType);
     else if (checklist->request)
-        return data->match(checklist->request->errType);
+        return data->match(checklist->request->error.category);
     return 0;
 }
 
index 484ab193da310d70e2c4f0d66288ea59442ab3eb..56c9a65320d2831a3e9777c1ffff52e5ef1df7b9 100644 (file)
@@ -10,7 +10,7 @@
 #define SQUID_ACLSQUIDERROR_H
 
 #include "acl/Strategy.h"
-#include "err_type.h"
+#include "error/forward.h"
 
 class ACLSquidErrorStrategy : public ACLStrategy<err_type>
 {
index 14603a6f736e8c8f9be106da4c2fd460c50f065f..a1c9a8e25254e4eda8a09408b3f2ddc03da94d6f 100644 (file)
@@ -12,7 +12,7 @@
 #include "cache_cf.h"
 #include "ConfigParser.h"
 #include "Debug.h"
-#include "err_type.h"
+#include "error/Error.h"
 #include "fatal.h"
 #include "wordlist.h"
 
index 9919a1c9a3129c77cb63f446cd4be6b47a5b0d3e..0806cb56237dd19f86876362bf9ebfcd9c5b5294 100644 (file)
@@ -11,7 +11,7 @@
 
 #include "acl/Data.h"
 #include "base/CbDataList.h"
-#include "err_type.h"
+#include "error/forward.h"
 
 /// \ingroup ACLAPI
 class ACLSquidErrorData : public ACLData<err_type>
index a73f32c547e39599c667992b168fad22f706aa23..bb4da5d91fbfa5b59c522c9cefba1e796a97a095 100644 (file)
@@ -24,7 +24,8 @@
 #include "base64.h"
 #include "comm.h"
 #include "comm/Connection.h"
-#include "err_detail_type.h"
+#include "error/Detail.h"
+#include "error/ExceptionErrorDetail.h"
 #include "http/ContentLengthInterpreter.h"
 #include "HttpHeaderTools.h"
 #include "HttpReply.h"
@@ -670,9 +671,9 @@ void Adaptation::Icap::ModXact::callException(const std::exception &e)
     if (!canStartBypass || isRetriable) {
         if (!isRetriable) {
             if (const TextException *te = dynamic_cast<const TextException *>(&e))
-                detailError(ERR_DETAIL_EXCEPTION_START + te->id());
+                detailError(new ExceptionErrorDetail(te->id()));
             else
-                detailError(ERR_DETAIL_EXCEPTION_OTHER);
+                detailError(new ExceptionErrorDetail(Here().id()));
         }
         Adaptation::Icap::Xaction::callException(e);
         return;
@@ -683,10 +684,10 @@ void Adaptation::Icap::ModXact::callException(const std::exception &e)
                e.what() << ' ' << status());
         bypassFailure();
     } catch (const TextException &bypassTe) {
-        detailError(ERR_DETAIL_EXCEPTION_START + bypassTe.id());
+        detailError(new ExceptionErrorDetail(bypassTe.id()));
         Adaptation::Icap::Xaction::callException(bypassTe);
     } catch (const std::exception &bypassE) {
-        detailError(ERR_DETAIL_EXCEPTION_OTHER);
+        detailError(new ExceptionErrorDetail(Here().id()));
         Adaptation::Icap::Xaction::callException(bypassE);
     }
 }
@@ -1281,7 +1282,8 @@ void Adaptation::Icap::ModXact::noteMoreBodySpaceAvailable(BodyPipe::Pointer)
 // adapted body consumer aborted
 void Adaptation::Icap::ModXact::noteBodyConsumerAborted(BodyPipe::Pointer)
 {
-    detailError(ERR_DETAIL_ICAP_XACT_BODY_CONSUMER_ABORT);
+    static const auto d = MakeNamedErrorDetail("ICAP_XACT_BODY_CONSUMER_ABORT");
+    detailError(d);
     mustStop("adapted body consumer aborted");
 }
 
@@ -1299,8 +1301,10 @@ void Adaptation::Icap::ModXact::swanSong()
     stopWriting(false);
     stopSending(false);
 
-    if (theInitiator.set()) // we have not sent the answer to the initiator
-        detailError(ERR_DETAIL_ICAP_XACT_OTHER);
+    if (theInitiator.set()) { // we have not sent the answer to the initiator
+        static const auto d = MakeNamedErrorDetail("ICAP_XACT_OTHER");
+        detailError(d);
+    }
 
     // update adaptation history if start was called and we reserved a slot
     Adaptation::History::Pointer ah = virginRequest().adaptLogHistory();
@@ -1989,7 +1993,7 @@ bool Adaptation::Icap::ModXact::fillVirginHttpHeader(MemBuf &mb) const
     return true;
 }
 
-void Adaptation::Icap::ModXact::detailError(int errDetail)
+void Adaptation::Icap::ModXact::detailError(const ErrorDetail::Pointer &errDetail)
 {
     HttpRequest *request = dynamic_cast<HttpRequest*>(adapted.header);
     // if no adapted request, update virgin (and inherit its properties later)
index d6c541bdb3dec45ed3bc78dacec259c0c75ea6cc..5abbc848ba245876ddc7242115868e0db07d0fd4 100644 (file)
@@ -174,7 +174,7 @@ public:
     virtual void callException(const std::exception &e);
 
     /// record error detail in the virgin request if possible
-    virtual void detailError(int errDetail);
+    virtual void detailError(const ErrorDetail::Pointer &errDetail);
     // Icap::Xaction API
     virtual void clearError();
     /// The master transaction log entry
index b1227dfee9d2f392bc834f2fa8f58d2463d597e5..c904bc7dd612c166f8697ee92b292542b96f853f 100644 (file)
@@ -20,7 +20,7 @@
 #include "comm/Read.h"
 #include "comm/Write.h"
 #include "CommCalls.h"
-#include "err_detail_type.h"
+#include "error/Detail.h"
 #include "fde.h"
 #include "FwdState.h"
 #include "globals.h"
@@ -324,7 +324,8 @@ void Adaptation::Icap::Xaction::dieOnConnectionFailure()
     debugs(93, 2, HERE << typeName <<
            " failed to connect to " << service().cfg().uri);
     service().noteConnectionFailed("failure");
-    detailError(ERR_DETAIL_ICAP_XACT_START);
+    static const auto d = MakeNamedErrorDetail("ICAP_XACT_START");
+    detailError(d);
     throw TexcHere("cannot connect to the ICAP service");
 }
 
@@ -394,7 +395,8 @@ void Adaptation::Icap::Xaction::noteCommClosed(const CommCloseCbParams &)
 
 void Adaptation::Icap::Xaction::handleCommClosed()
 {
-    detailError(ERR_DETAIL_ICAP_XACT_CLOSE);
+    static const auto d = MakeNamedErrorDetail("ICAP_XACT_CLOSE");
+    detailError(d);
     mustStop("ICAP service connection externally closed");
 }
 
@@ -575,7 +577,8 @@ void Adaptation::Icap::Xaction::noteInitiatorAborted()
     if (theInitiator.set()) {
         debugs(93,4, HERE << "Initiator gone before ICAP transaction ended");
         clearInitiator();
-        detailError(ERR_DETAIL_ICAP_INIT_GONE);
+        static const auto d = MakeNamedErrorDetail("ICAP_INIT_GONE");
+        detailError(d);
         setOutcome(xoGone);
         mustStop("initiator aborted");
     }
@@ -754,7 +757,8 @@ Adaptation::Icap::Xaction::handleSecuredPeer(Security::EncryptorAnswer &answer)
         debugs(93, 2, typeName <<
                " TLS negotiation to " << service().cfg().uri << " failed");
         service().noteConnectionFailed("failure");
-        detailError(ERR_DETAIL_ICAP_XACT_SSL_START);
+        static const auto d = MakeNamedErrorDetail("ICAP_XACT_SSL_START");
+        detailError(d);
         throw TexcHere("cannot connect to the TLS ICAP service");
     }
 
index 03abd200e660964130dad5ac8add1b24aa664c0e..1f6cd165e1dadb5ecab8e1dd134a379d0c5c90bf 100644 (file)
@@ -13,6 +13,7 @@
 #include "adaptation/icap/ServiceRep.h"
 #include "adaptation/Initiate.h"
 #include "comm/ConnOpener.h"
+#include "error/forward.h"
 #include "HttpReply.h"
 #include "ipcache.h"
 #include "sbuf/SBuf.h"
@@ -73,7 +74,7 @@ protected:
 
     void handleSecuredPeer(Security::EncryptorAnswer &answer);
     /// record error detail if possible
-    virtual void detailError(int) {}
+    virtual void detailError(const ErrorDetailPointer &) {}
 
     void openConnection();
     void closeConnection();
index d6d84164e8acb27f9cbc358963317b68a9b3ba93..257ca20f8e9a07572defac7345e50177b65cdd4b 100644 (file)
@@ -58,6 +58,13 @@ TextException::what() const throw()
     return result.what();
 }
 
+std::ostream &
+operator <<(std::ostream &os, const TextException &ex)
+{
+    ex.print(os);
+    return os;
+}
+
 std::ostream &
 CurrentException(std::ostream &os)
 {
@@ -65,6 +72,9 @@ CurrentException(std::ostream &os)
         try {
             throw; // re-throw to recognize the exception type
         }
+        catch (const TextException &ex) {
+            os << ex; // optimization: this is a lot cheaper than what() below
+        }
         catch (const std::exception &ex) {
             os << ex.what();
         }
index edbcf664acffda89c1e090fdb3da91a8efd3ed09..2a43fac358e3d85cf8e35378ef9ccd2bd9587298 100644 (file)
@@ -51,6 +51,9 @@ public:
 /// prints active (i.e., thrown but not yet handled) exception
 std::ostream &CurrentException(std::ostream &);
 
+/// efficiently prints TextException
+std::ostream &operator <<(std::ostream &, const TextException &);
+
 /// legacy convenience macro; it is not difficult to type Here() now
 #define TexcHere(msg) TextException((msg), Here())
 
index 4aed1a53e00d197d4e615bdd40ef38fdaa829b60..f64e0041aabb85abd482bffbee744f9c33ec636f 100644 (file)
@@ -76,6 +76,7 @@
 #include "comm/TcpAcceptor.h"
 #include "comm/Write.h"
 #include "CommCalls.h"
+#include "error/ExceptionErrorDetail.h"
 #include "errorpage.h"
 #include "fd.h"
 #include "fde.h"
 #include "profiler/Profiler.h"
 #include "proxyp/Header.h"
 #include "proxyp/Parser.h"
+#include "sbuf/Stream.h"
+#include "security/Io.h"
 #include "security/NegotiationHistory.h"
 #include "servers/forward.h"
 #include "SquidConfig.h"
@@ -175,7 +178,6 @@ private:
 static void clientListenerConnectionOpened(AnyP::PortCfgPointer &s, const Ipc::FdNoteId portTypeNote, const Subscription::Pointer &sub);
 
 static IOACB httpAccept;
-static CTCB clientLifetimeTimeout;
 #if USE_IDENT
 static IDCB clientIdentDone;
 #endif
@@ -315,7 +317,7 @@ ClientHttpRequest::updateCounters()
 {
     clientUpdateStatCounters(logType);
 
-    if (request->errType != ERR_NONE)
+    if (request->error)
         ++ statCounter.client_http.errors;
 
     clientUpdateStatHistCounters(logType,
@@ -372,8 +374,7 @@ prepareLogWithRequestDetails(HttpRequest * request, AccessLogEntry::Pointer &aLo
     // TODO: avoid losses by keeping these stats in a shared history object?
     if (aLogEntry->request) {
         aLogEntry->request->dnsWait = request->dnsWait;
-        aLogEntry->request->errType = request->errType;
-        aLogEntry->request->errDetail = request->errDetail;
+        aLogEntry->request->error = request->error;
     }
 }
 
@@ -586,21 +587,38 @@ ConnStateData::setAuth(const Auth::UserRequest::Pointer &aur, const char *by)
 }
 #endif
 
+void
+ConnStateData::resetReadTimeout(const time_t timeout)
+{
+    typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
+    AsyncCall::Pointer callback = JobCallback(33, 5, TimeoutDialer, this, ConnStateData::requestTimeout);
+    commSetConnTimeout(clientConnection, timeout, callback);
+}
+
+void
+ConnStateData::extendLifetime()
+{
+    typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
+    AsyncCall::Pointer callback = JobCallback(5, 4, TimeoutDialer, this, ConnStateData::lifetimeTimeout);
+    commSetConnTimeout(clientConnection, Config.Timeout.lifetime, callback);
+}
+
 // cleans up before destructor is called
 void
 ConnStateData::swanSong()
 {
     debugs(33, 2, HERE << clientConnection);
-    checkLogging();
 
     flags.readMore = false;
     clientdbEstablished(clientConnection->remote, -1);  /* decrement */
-    pipeline.terminateAll(0);
+
+    terminateAll(ERR_NONE, LogTagsErrors());
+    checkLogging();
 
     // XXX: Closing pinned conn is too harsh: The Client may want to continue!
     unpinConnection(true);
 
-    Server::swanSong(); // closes the client connection
+    Server::swanSong();
 
 #if USE_AUTH
     // NP: do this bit after closing the connections to avoid side effects from unwanted TCP RST
@@ -610,6 +628,31 @@ ConnStateData::swanSong()
     flags.swanSang = true;
 }
 
+void
+ConnStateData::callException(const std::exception &ex)
+{
+    Server::callException(ex); // logs ex and stops the job
+
+    ErrorDetail::Pointer errorDetail;
+    if (const auto tex = dynamic_cast<const TextException*>(&ex))
+        errorDetail = new ExceptionErrorDetail(tex->id());
+    else
+        errorDetail = new ExceptionErrorDetail(Here().id());
+    updateError(ERR_GATEWAY_FAILURE, errorDetail);
+}
+
+void
+ConnStateData::updateError(const Error &error)
+{
+    if (const auto context = pipeline.front()) {
+        const auto http = context->http;
+        assert(http);
+        http->updateError(error);
+    } else {
+        bareError.update(error);
+    }
+}
+
 bool
 ConnStateData::isOpen() const
 {
@@ -864,10 +907,7 @@ ConnStateData::readNextRequest()
     /**
      * Set the timeout BEFORE calling readSomeData().
      */
-    typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
-    AsyncCall::Pointer timeoutCall = JobCallback(33, 5,
-                                     TimeoutDialer, this, ConnStateData::requestTimeout);
-    commSetConnTimeout(clientConnection, clientConnection->timeLeft(idleTimeout()), timeoutCall);
+    resetReadTimeout(clientConnection->timeLeft(idleTimeout()));
 
     readSomeData();
     /** Please don't do anything with the FD past here! */
@@ -1413,21 +1453,22 @@ ConnStateData::parseHttpRequest(const Http1::RequestParserPointer &hp)
 }
 
 bool
-ConnStateData::connFinishedWithConn(int size)
-{
-    if (size == 0) {
-        if (pipeline.empty() && inBuf.isEmpty()) {
-            /* no current or pending requests */
-            debugs(33, 4, HERE << clientConnection << " closed");
-            return true;
-        } else if (!Config.onoff.half_closed_clients) {
-            /* admin doesn't want to support half-closed client sockets */
-            debugs(33, 3, HERE << clientConnection << " aborted (half_closed_clients disabled)");
-            pipeline.terminateAll(0);
-            return true;
-        }
+ConnStateData::shouldCloseOnEof() const
+{
+    if (pipeline.empty() && inBuf.isEmpty()) {
+        debugs(33, 4, "yes, without active requests and unparsed input");
+        return true;
+    }
+
+    if (!Config.onoff.half_closed_clients) {
+        debugs(33, 3, "yes, without half_closed_clients");
+        return true;
     }
 
+    // Squid currently tries to parse (possibly again) a partially received
+    // request after an EOF with half_closed_clients. To give that last parse in
+    // afterClientRead() a chance, we ignore partially parsed requests here.
+    debugs(33, 3, "no, honoring half_closed_clients");
     return false;
 }
 
@@ -1487,7 +1528,7 @@ bool ConnStateData::serveDelayedError(Http::Stream *context)
         repContext->setReplyToStoreEntry(sslServerBump->entry, "delayed SslBump error");
 
         // Get error details from the fake certificate-peeking request.
-        http->request->detailError(sslServerBump->request->errType, sslServerBump->request->errDetail);
+        http->request->error.update(sslServerBump->request->error);
         context->pullData();
         return true;
     }
@@ -1524,10 +1565,10 @@ bool ConnStateData::serveDelayedError(Http::Stream *context)
                 // Create an error object and fill it
                 const auto err = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request, http->al);
                 err->src_addr = clientConnection->remote;
-                Ssl::ErrorDetail *errDetail = new Ssl::ErrorDetail(
+                const Security::ErrorDetail::Pointer errDetail = new Security::ErrorDetail(
                     SQUID_X509_V_ERR_DOMAIN_MISMATCH,
-                    srvCert.get(), nullptr);
-                err->detail = errDetail;
+                    srvCert, nullptr);
+                updateError(ERR_SECURE_CONNECT_FAIL, errDetail);
                 repContext->setReplyToError(request->method, err);
                 assert(context->http->out.offset == 0);
                 context->pullData();
@@ -1755,6 +1796,20 @@ clientProcessRequest(ConnStateData *conn, const Http1::RequestParserPointer &hp,
     clientProcessRequestFinished(conn, request);
 }
 
+void
+ConnStateData::add(const Http::StreamPointer &context)
+{
+    debugs(33, 3, context << " to " << pipeline.count() << '/' << pipeline.nrequests);
+    if (bareError) {
+        debugs(33, 5, "assigning " << bareError);
+        assert(context);
+        assert(context->http);
+        context->http->updateError(bareError);
+        bareError.clear();
+    }
+    pipeline.add(context);
+}
+
 int
 ConnStateData::pipelinePrefetchMax() const
 {
@@ -1873,11 +1928,7 @@ ConnStateData::receivedFirstByte()
         return;
 
     receivedFirstByte_ = true;
-    // Set timeout to Config.Timeout.request
-    typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
-    AsyncCall::Pointer timeoutCall =  JobCallback(33, 5,
-                                      TimeoutDialer, this, ConnStateData::requestTimeout);
-    commSetConnTimeout(clientConnection, Config.Timeout.request, timeoutCall);
+    resetReadTimeout(Config.Timeout.request);
 }
 
 /**
@@ -1924,11 +1975,7 @@ ConnStateData::clientParseRequests()
 
         if (Http::StreamPointer context = parseOneRequest()) {
             debugs(33, 5, clientConnection << ": done parsing a request");
-
-            AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "clientLifetimeTimeout",
-                                             CommTimeoutCbPtrFun(clientLifetimeTimeout, context->http));
-            commSetConnTimeout(clientConnection, Config.Timeout.lifetime, timeoutCall);
-
+            extendLifetime();
             context->registerWithConn();
 
 #if USE_OPENSSL
@@ -1973,14 +2020,9 @@ ConnStateData::afterClientRead()
     if (!clientParseRequests()) {
         if (!isOpen())
             return;
-        /*
-         * If the client here is half closed and we failed
-         * to parse a request, close the connection.
-         * The above check with connFinishedWithConn() only
-         * succeeds _if_ the buffer is empty which it won't
-         * be if we have an incomplete request.
-         * XXX: This duplicates ConnStateData::kick
-         */
+        // We may get here if the client half-closed after sending a partial
+        // request. See doClientRead() and shouldCloseOnEof().
+        // XXX: This partially duplicates ConnStateData::kick().
         if (pipeline.empty() && commIsHalfClosed(clientConnection->fd)) {
             debugs(33, 5, clientConnection << ": half-closed connection, no completed request parsed, connection closing.");
             clientConnection->close();
@@ -2148,6 +2190,7 @@ ConnStateData::requestTimeout(const CommTimeoutCbParams &io)
         return;
 
     const err_type error = receivedFirstByte_ ? ERR_REQUEST_PARSE_TIMEOUT : ERR_REQUEST_START_TIMEOUT;
+    updateError(error);
     if (tunnelOnError(HttpRequestMethod(), error))
         return;
 
@@ -2163,16 +2206,15 @@ ConnStateData::requestTimeout(const CommTimeoutCbParams &io)
     io.conn->close();
 }
 
-static void
-clientLifetimeTimeout(const CommTimeoutCbParams &io)
+void
+ConnStateData::lifetimeTimeout(const CommTimeoutCbParams &io)
 {
-    ClientHttpRequest *http = static_cast<ClientHttpRequest *>(io.data);
-    debugs(33, DBG_IMPORTANT, "WARNING: Closing client connection due to lifetime timeout");
-    debugs(33, DBG_IMPORTANT, "\t" << http->uri);
-    if (const auto conn = http->getConn())
-        conn->pipeline.terminateAll(ETIMEDOUT);
-    if (Comm::IsConnOpen(io.conn))
-        io.conn->close();
+    debugs(33, DBG_IMPORTANT, "WARNING: Closing client connection due to lifetime timeout" <<
+           Debug::Extra << "connection: " << io.conn);
+
+    LogTagsErrors lte;
+    lte.timedout = true;
+    terminateAll(ERR_LIFETIME_EXP, lte);
 }
 
 ConnStateData::ConnStateData(const MasterXaction::Pointer &xact) :
@@ -2339,93 +2381,32 @@ httpsCreate(const ConnStateData *connState, const Security::ContextPointer &ctx)
     return false;
 }
 
-/**
- *
- * \retval 1 on success
- * \retval 0 when needs more data
- * \retval -1 on error
- */
-static int
-tlsAttemptHandshake(ConnStateData *conn, PF *callback)
-{
-    // TODO: maybe throw instead of returning -1
-    // see https://github.com/squid-cache/squid/pull/81#discussion_r153053278
-    int fd = conn->clientConnection->fd;
-    auto session = fd_table[fd].ssl.get();
-
-    errno = 0;
-
-#if USE_OPENSSL
-    const auto ret = SSL_accept(session);
-    if (ret > 0)
-        return 1;
-
-    const int xerrno = errno;
-    const auto ssl_error = SSL_get_error(session, ret);
-
-    switch (ssl_error) {
-
-    case SSL_ERROR_WANT_READ:
-        Comm::SetSelect(fd, COMM_SELECT_READ, callback, (callback ? conn : nullptr), 0);
-        return 0;
-
-    case SSL_ERROR_WANT_WRITE:
-        Comm::SetSelect(fd, COMM_SELECT_WRITE, callback, (callback ? conn : nullptr), 0);
-        return 0;
-
-    case SSL_ERROR_SYSCALL:
-        if (ret == 0) {
-            debugs(83, 2, "Error negotiating SSL connection on FD " << fd << ": Aborted by client: " << ssl_error);
-        } else {
-            debugs(83, (xerrno == ECONNRESET) ? 1 : 2, "Error negotiating SSL connection on FD " << fd << ": " <<
-                   (xerrno == 0 ? Security::ErrorString(ssl_error) : xstrerr(xerrno)));
-        }
-        break;
-
-    case SSL_ERROR_ZERO_RETURN:
-        debugs(83, DBG_IMPORTANT, "Error negotiating SSL connection on FD " << fd << ": Closed by client");
-        break;
-
-    default:
-        debugs(83, DBG_IMPORTANT, "Error negotiating SSL connection on FD " <<
-               fd << ": " << Security::ErrorString(ssl_error) <<
-               " (" << ssl_error << "/" << ret << ")");
-    }
-
-#elif USE_GNUTLS
-
-    const auto x = gnutls_handshake(session);
-    if (x == GNUTLS_E_SUCCESS)
-        return 1;
-
-    if (gnutls_error_is_fatal(x)) {
-        debugs(83, 2, "Error negotiating TLS on " << conn->clientConnection << ": Aborted by client: " << Security::ErrorString(x));
-
-    } else if (x == GNUTLS_E_INTERRUPTED || x == GNUTLS_E_AGAIN) {
-        const auto ioAction = (gnutls_record_get_direction(session)==0 ? COMM_SELECT_READ : COMM_SELECT_WRITE);
-        Comm::SetSelect(fd, ioAction, callback, (callback ? conn : nullptr), 0);
-        return 0;
-    }
-
-#else
-    // Performing TLS handshake should never be reachable without a TLS/SSL library.
-    (void)session; // avoid compiler and static analysis complaints
-    fatal("FATAL: HTTPS not supported by this Squid.");
-#endif
-
-    return -1;
-}
-
 /** negotiate an SSL connection */
 static void
 clientNegotiateSSL(int fd, void *data)
 {
     ConnStateData *conn = (ConnStateData *)data;
 
-    const int ret = tlsAttemptHandshake(conn, clientNegotiateSSL);
-    if (ret <= 0) {
-        if (ret < 0) // An error
-            conn->clientConnection->close();
+    const auto handshakeResult = Security::Accept(*conn->clientConnection);
+    switch (handshakeResult.category) {
+    case Security::IoResult::ioSuccess:
+        break;
+
+    case Security::IoResult::ioWantRead:
+        Comm::SetSelect(conn->clientConnection->fd, COMM_SELECT_READ, clientNegotiateSSL, conn, 0);
+        return;
+
+    case Security::IoResult::ioWantWrite:
+        Comm::SetSelect(conn->clientConnection->fd, COMM_SELECT_WRITE, clientNegotiateSSL, conn, 0);
+        return;
+
+    case Security::IoResult::ioError:
+        debugs(83, (handshakeResult.important ? DBG_IMPORTANT : 2), "ERROR: " << handshakeResult.errorDescription <<
+               " while accepting a TLS connection on " << conn->clientConnection << ": " << handshakeResult.errorDetail);
+        // TODO: No ConnStateData::tunnelOnError() on this forward-proxy code
+        // path because we cannot know the intended connection target?
+        conn->updateError(ERR_SECURE_ACCEPT_FAIL, handshakeResult.errorDetail);
+        conn->clientConnection->close();
         return;
     }
 
@@ -2501,6 +2482,15 @@ clientNegotiateSSL(int fd, void *data)
     debugs(83, 2, "Client certificate requesting not yet implemented.");
 #endif
 
+    // If we are called, then bumped CONNECT has succeeded. Finalize it.
+    if (auto xact = conn->pipeline.front()) {
+        if (xact->http && xact->http->request && xact->http->request->method == Http::METHOD_CONNECT)
+            xact->finished();
+        // cannot proceed with encryption if requests wait for plain responses
+        Must(conn->pipeline.empty());
+    }
+    /* careful: finished() above frees request, host, etc. */
+
     conn->readSomeData();
 }
 
@@ -2517,10 +2507,7 @@ httpsEstablish(ConnStateData *connState, const Security::ContextPointer &ctx)
     if (!ctx || !httpsCreate(connState, ctx))
         return;
 
-    typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
-    AsyncCall::Pointer timeoutCall = JobCallback(33, 5, TimeoutDialer,
-                                     connState, ConnStateData::requestTimeout);
-    commSetConnTimeout(details, Config.Timeout.request, timeoutCall);
+    connState->resetReadTimeout(Config.Timeout.request);
 
     Comm::SetSelect(details->fd, COMM_SELECT_READ, clientNegotiateSSL, connState, 0);
 }
@@ -2622,6 +2609,7 @@ ConnStateData::postHttpsAccept()
         acl_checklist->al->cache.port = port;
         acl_checklist->al->cache.caddr = log_addr;
         acl_checklist->al->proxyProtocolHeader = proxyProtocolHeader_;
+        acl_checklist->al->updateError(bareError);
         HTTPMSGUNLOCK(acl_checklist->al->request);
         acl_checklist->al->request = request;
         HTTPMSGLOCK(acl_checklist->al->request);
@@ -2798,15 +2786,6 @@ ConnStateData::storeTlsContextToCache(const SBuf &cacheKey, Security::ContextPoi
 void
 ConnStateData::getSslContextStart()
 {
-    // If we are called, then CONNECT has succeeded. Finalize it.
-    if (auto xact = pipeline.front()) {
-        if (xact->http && xact->http->request && xact->http->request->method == Http::METHOD_CONNECT)
-            xact->finished();
-        // cannot proceed with encryption if requests wait for plain responses
-        Must(pipeline.empty());
-    }
-    /* careful: finished() above frees request, host, etc. */
-
     if (port->secure.generateHostCertificates) {
         Ssl::CertificateProperties certProperties;
         buildSslCertGenerationParams(certProperties);
@@ -2889,10 +2868,7 @@ ConnStateData::getSslContextDone(Security::ContextPointer &ctx)
     // bumped intercepted conns should already have Config.Timeout.request set
     // but forwarded connections may only have Config.Timeout.lifetime. [Re]set
     // to make sure the connection does not get stuck on non-SSL clients.
-    typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
-    AsyncCall::Pointer timeoutCall = JobCallback(33, 5, TimeoutDialer,
-                                     this, ConnStateData::requestTimeout);
-    commSetConnTimeout(clientConnection, Config.Timeout.request, timeoutCall);
+    resetReadTimeout(Config.Timeout.request);
 
     switchedToHttps_ = true;
 
@@ -2941,10 +2917,7 @@ ConnStateData::switchToHttps(ClientHttpRequest *http, Ssl::BumpMode bumpServerMo
 
     // commSetConnTimeout() was called for this request before we switched.
     // Fix timeout to request_start_timeout
-    typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
-    AsyncCall::Pointer timeoutCall =  JobCallback(33, 5,
-                                      TimeoutDialer, this, ConnStateData::requestTimeout);
-    commSetConnTimeout(clientConnection, Config.Timeout.request_start_timeout, timeoutCall);
+    resetReadTimeout(Config.Timeout.request_start_timeout);
     // Also reset receivedFirstByte_ flag to allow this timeout work in the case we have
     // a bumbed "connect" request on non transparent port.
     receivedFirstByte_ = false;
@@ -2969,7 +2942,9 @@ ConnStateData::parseTlsHandshake()
     receivedFirstByte();
     fd_note(clientConnection->fd, "Parsing TLS handshake");
 
-    bool unsupportedProtocol = false;
+    // stops being nil if we fail to parse the handshake
+    ErrorDetail::Pointer parseErrorDetails;
+
     try {
         if (!tlsParser.parseHello(inBuf)) {
             // need more data to finish parsing
@@ -2977,9 +2952,14 @@ ConnStateData::parseTlsHandshake()
             return;
         }
     }
-    catch (const std::exception &ex) {
-        debugs(83, 2, "error on FD " << clientConnection->fd << ": " << ex.what());
-        unsupportedProtocol = true;
+    catch (const TextException &ex) {
+        debugs(83, 2, "exception: " << ex);
+        parseErrorDetails = new ExceptionErrorDetail(ex.id());
+    }
+    catch (...) {
+        debugs(83, 2, "exception: " << CurrentException);
+        static const auto d = MakeNamedErrorDetail("TLS_ACCEPT_PARSE");
+        parseErrorDetails = d;
     }
 
     parsingTlsHandshake = false;
@@ -3000,13 +2980,12 @@ ConnStateData::parseTlsHandshake()
     // We should disable read/write handlers
     Comm::ResetSelect(clientConnection->fd);
 
-    if (unsupportedProtocol) {
+    if (parseErrorDetails) {
         Http::StreamPointer context = pipeline.front();
         Must(context && context->http);
         HttpRequest::Pointer request = context->http->request;
         debugs(83, 5, "Got something other than TLS Client Hello. Cannot SslBump.");
-        sslBumpMode = Ssl::bumpSplice;
-        context->http->al->ssl.bumpMode = Ssl::bumpSplice;
+        updateError(ERR_PROTOCOL_UNKNOWN, parseErrorDetails);
         if (!clientTunnelOnError(this, context, request, HttpRequestMethod(), ERR_PROTOCOL_UNKNOWN))
             clientConnection->close();
         return;
@@ -3130,17 +3109,15 @@ ConnStateData::startPeekAndSplice()
     bio->setReadBufData(inBuf);
     bio->hold(true);
 
-    // Here squid should have all of the client hello message so the
-    // tlsAttemptHandshake() should return 0.
-    // This block exist only to force openSSL parse client hello and detect
-    // ERR_SECURE_ACCEPT_FAIL error, which should be checked and splice if required.
-    if (tlsAttemptHandshake(this, nullptr) < 0) {
-        debugs(83, 2, "TLS handshake failed.");
-        HttpRequest::Pointer request(http ? http->request : nullptr);
-        if (!clientTunnelOnError(this, context, request, HttpRequestMethod(), ERR_SECURE_ACCEPT_FAIL))
-            clientConnection->close();
-        return;
-    }
+    // We have successfully parsed client Hello, but our TLS handshake parser is
+    // forgiving. Now we use a TLS library to parse the same bytes, so that we
+    // can honor on_unsupported_protocol if needed. If there are no errors, we
+    // expect Security::Accept() to ask us to write (our) TLS server Hello. We
+    // also allow an ioWantRead result in case some fancy TLS extension that
+    // Squid does not yet understand requires reading post-Hello client bytes.
+    const auto handshakeResult = Security::Accept(*clientConnection);
+    if (!handshakeResult.wantsIo())
+        return handleSslBumpHandshakeError(handshakeResult);
 
     // We need to reset inBuf here, to be used by incoming requests in the case
     // of SSL bump
@@ -3150,6 +3127,43 @@ ConnStateData::startPeekAndSplice()
     FwdState::Start(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw(), http ? http->al : NULL);
 }
 
+/// process a problematic Security::Accept() result on the SslBump code path
+void
+ConnStateData::handleSslBumpHandshakeError(const Security::IoResult &handshakeResult)
+{
+    auto errCategory = ERR_NONE;
+
+    switch (handshakeResult.category) {
+    case Security::IoResult::ioSuccess: {
+        static const auto d = MakeNamedErrorDetail("TLS_ACCEPT_UNEXPECTED_SUCCESS");
+        updateError(errCategory = ERR_GATEWAY_FAILURE, d);
+        break;
+    }
+
+    case Security::IoResult::ioWantRead: {
+        static const auto d = MakeNamedErrorDetail("TLS_ACCEPT_UNEXPECTED_READ");
+        updateError(errCategory = ERR_GATEWAY_FAILURE, d);
+        break;
+    }
+
+    case Security::IoResult::ioWantWrite: {
+        static const auto d = MakeNamedErrorDetail("TLS_ACCEPT_UNEXPECTED_WRITE");
+        updateError(errCategory = ERR_GATEWAY_FAILURE, d);
+        break;
+    }
+
+    case Security::IoResult::ioError:
+        debugs(83, (handshakeResult.important ? DBG_IMPORTANT : 2), "ERROR: " << handshakeResult.errorDescription <<
+               " while SslBump-accepting a TLS connection on " << clientConnection << ": " << handshakeResult.errorDetail);
+        updateError(errCategory = ERR_SECURE_ACCEPT_FAIL, handshakeResult.errorDetail);
+        break;
+
+    }
+
+    if (!tunnelOnError(HttpRequestMethod(), errCategory))
+        clientConnection->close();
+}
+
 void
 ConnStateData::doPeekAndSpliceStep()
 {
@@ -3207,7 +3221,13 @@ ConnStateData::initiateTunneledRequest(HttpRequest::Pointer const &cause, Http::
         connectHost = clientConnection->local.toStr(ip, sizeof(ip));
         connectPort = clientConnection->local.port();
     } else {
+        // Typical cases are malformed HTTP requests on http_port and malformed
+        // TLS handshakes on non-bumping https_port. TODO: Discover these
+        // problems earlier so that they can be classified/detailed better.
         debugs(33, 2, "Not able to compute URL, abort request tunneling for " << reason);
+        // TODO: throw when nonBlockingCheck() callbacks gain job protections
+        static const auto d = MakeNamedErrorDetail("TUNNEL_TARGET");
+        updateError(ERR_INVALID_REQ, d);
         return false;
     }
 
@@ -3268,11 +3288,7 @@ ConnStateData::buildFakeRequest(Http::MethodType const method, SBuf &useHost, un
 
     stream->flags.parsed_ok = 1; // Do we need it?
     stream->mayUseConnection(true);
-
-    AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "clientLifetimeTimeout",
-                                     CommTimeoutCbPtrFun(clientLifetimeTimeout, stream->http));
-    commSetConnTimeout(clientConnection, Config.Timeout.lifetime, timeoutCall);
-
+    extendLifetime();
     stream->registerWithConn();
 
     MasterXaction::Pointer mx = new MasterXaction(XactionInitiator::initClient);
@@ -3985,21 +4001,45 @@ ConnStateData::unpinConnection(const bool andClose)
 }
 
 void
-ConnStateData::checkLogging()
+ConnStateData::terminateAll(const Error &error, const LogTagsErrors &lte)
 {
-    // if we are parsing request body, its request is responsible for logging
-    if (bodyPipe)
-        return;
+    debugs(33, 3, pipeline.count() << '/' << pipeline.nrequests << " after " << error);
 
-    // a request currently using this connection is responsible for logging
-    if (!pipeline.empty() && pipeline.back()->mayUseConnection())
-        return;
+    if (pipeline.empty()) {
+        bareError.update(error); // XXX: bareLogTagsErrors
+    } else {
+        // We terminate the current CONNECT/PUT/etc. context below, logging any
+        // error details, but that context may leave unparsed bytes behind.
+        // Consume them to stop checkLogging() from logging them again later.
+        const auto intputToConsume =
+#if USE_OPENSSL
+            parsingTlsHandshake ? "TLS handshake" : // more specific than CONNECT
+#endif
+            bodyPipe ? "HTTP request body" :
+            pipeline.back()->mayUseConnection() ? "HTTP CONNECT" :
+            nullptr;
+
+        while (const auto context = pipeline.front()) {
+            context->noteIoError(error, lte);
+            context->finished(); // cleanup and self-deregister
+            assert(context != pipeline.front());
+        }
 
-    /* Either we are waiting for the very first transaction, or
-     * we are done with the Nth transaction and are waiting for N+1st.
-     * XXX: We assume that if anything was added to inBuf, then it could
-     * only be consumed by actions already covered by the above checks.
-     */
+        if (intputToConsume && !inBuf.isEmpty()) {
+            debugs(83, 5, "forgetting client " << intputToConsume << " bytes: " << inBuf.length());
+            inBuf.clear();
+        }
+    }
+
+    clientConnection->close();
+}
+
+/// log the last (attempt at) transaction if nobody else did
+void
+ConnStateData::checkLogging()
+{
+    // to simplify our logic, we assume that terminateAll() has been called
+    assert(pipeline.empty());
 
     // do not log connections that closed after a transaction (it is normal)
     // TODO: access_log needs ACLs to match received-no-bytes connections
@@ -4011,6 +4051,7 @@ ConnStateData::checkLogging()
     http.req_sz = inBuf.length();
     // XXX: Or we died while waiting for the pinned connection to become idle.
     http.setErrorUri("error:transaction-end-before-headers");
+    http.updateError(bareError);
 }
 
 bool
index e3e99661ed4252242d36e1a688fb97b52d4cfa4a..b4a0bdd693166333dede1b95799608ff8279254e 100644 (file)
 #ifndef SQUID_CLIENTSIDE_H
 #define SQUID_CLIENTSIDE_H
 
+#include "acl/forward.h"
 #include "base/RunnersRegistry.h"
 #include "clientStreamForward.h"
 #include "comm.h"
+#include "error/Error.h"
 #include "helper/forward.h"
 #include "http/forward.h"
 #include "HttpControlMsg.h"
@@ -26,6 +28,7 @@
 #include "auth/UserRequest.h"
 #endif
 #if USE_OPENSSL
+#include "security/forward.h"
 #include "security/Handshake.h"
 #include "ssl/support.h"
 #endif
@@ -157,6 +160,11 @@ public:
     /// note response sending error and close as soon as we read the request
     void stopSending(const char *error);
 
+    /// (re)sets timeout for receiving more bytes from the client
+    void resetReadTimeout(time_t timeout);
+    /// (re)sets client_lifetime timeout
+    void extendLifetime();
+
     void expectNoForwarding(); ///< cleans up virgin request [body] forwarding state
 
     /* BodyPipe API */
@@ -220,11 +228,13 @@ public:
     void clientReadFtpData(const CommIoCbParams &io);
     void connStateClosed(const CommCloseCbParams &io);
     void requestTimeout(const CommTimeoutCbParams &params);
+    void lifetimeTimeout(const CommTimeoutCbParams &params);
 
     // AsyncJob API
     virtual void start();
     virtual bool doneAll() const { return BodyProducer::doneAll() && false;}
     virtual void swanSong();
+    virtual void callException(const std::exception &);
 
     /// Changes state so that we close the connection and quit after serving
     /// the client-side-detected error response instead of getting stuck.
@@ -293,6 +303,9 @@ public:
 #endif
     char *prepareTlsSwitchingURL(const Http1::RequestParserPointer &hp);
 
+    /// registers a newly created stream
+    void add(const Http::StreamPointer &context);
+
     /// handle a control message received by context from a peer and call back
     virtual bool writeControlMsgAndCall(HttpReply *rep, AsyncCall::Pointer &call) = 0;
 
@@ -341,6 +354,17 @@ public:
 
     const ProxyProtocol::HeaderPointer &proxyProtocolHeader() const { return proxyProtocolHeader_; }
 
+    /// if necessary, stores new error information (if any)
+    void updateError(const Error &);
+
+    /// emplacement/convenience wrapper for updateError(const Error &)
+    void updateError(const err_type c, const ErrorDetailPointer &d) { updateError(Error(c, d)); }
+
+    // Exposed to be accessible inside the ClientHttpRequest constructor.
+    // TODO: Remove. Make sure there is always a suitable ALE instead.
+    /// a problem that occurred without a request (e.g., while parsing headers)
+    Error bareError;
+
 protected:
     void startDechunkingRequest();
     void finishDechunkingRequest(bool withSuccess);
@@ -393,8 +417,10 @@ protected:
 
 private:
     /* ::Server API */
-    virtual bool connFinishedWithConn(int size);
-    virtual void checkLogging();
+    virtual void terminateAll(const Error &, const LogTagsErrors &);
+    virtual bool shouldCloseOnEof() const;
+
+    void checkLogging();
 
     void clientAfterReadingRequests();
     bool concurrentRequestQueueFilled() const;
@@ -413,6 +439,7 @@ private:
     /// Attempts to add a given TLS context to the cache, replacing the old
     /// same-key context, if any
     void storeTlsContextToCache(const SBuf &cacheKey, Security::ContextPointer &ctx);
+    void handleSslBumpHandshakeError(const Security::IoResult &);
 #endif
 
     /// whether PROXY protocol header is still expected
index cde14e5e1719b7b349796a70a8249baa51ba1b8c..db24bba323d327818247fdc926ea66bb89d57c1e 100644 (file)
@@ -28,7 +28,7 @@
 #include "clientStream.h"
 #include "comm/Connection.h"
 #include "comm/Write.h"
-#include "err_detail_type.h"
+#include "error/Detail.h"
 #include "errorpage.h"
 #include "fd.h"
 #include "fde.h"
@@ -157,7 +157,7 @@ ClientHttpRequest::ClientHttpRequest(ConnStateData * aConn) :
     maxReplyBodySize_(0),
     entry_(NULL),
     loggingEntry_(NULL),
-    conn_(NULL)
+    conn_(cbdataReference(aConn))
 #if USE_OPENSSL
     , sslBumpNeed_(Ssl::bumpEnd)
 #endif
@@ -166,7 +166,6 @@ ClientHttpRequest::ClientHttpRequest(ConnStateData * aConn) :
     , request_satisfaction_offset(0)
 #endif
 {
-    setConn(aConn);
     al = new AccessLogEntry;
     CodeContext::Reset(al);
     al->cache.start_time = current_time;
@@ -175,6 +174,7 @@ ClientHttpRequest::ClientHttpRequest(ConnStateData * aConn) :
         al->cache.port = aConn->port;
         al->cache.caddr = aConn->log_addr;
         al->proxyProtocolHeader = aConn->proxyProtocolHeader();
+        al->updateError(aConn->bareError);
 
 #if USE_OPENSSL
         if (aConn->clientConnection != NULL && aConn->clientConnection->isOpen()) {
@@ -285,7 +285,7 @@ ClientHttpRequest::~ClientHttpRequest()
     loggingEntry(NULL);
 
     if (request)
-        checkFailureRatio(request->errType, al->hier.code);
+        checkFailureRatio(request->error.category, al->hier.code);
 
     freeResources();
 
@@ -1202,7 +1202,8 @@ ClientRequestContext::clientRedirectDone(const Helper::Reply &reply)
     switch (reply.result) {
     case Helper::TimedOut:
         if (Config.onUrlRewriteTimeout.action != toutActBypass) {
-            http->calloutsError(ERR_GATEWAY_FAILURE, ERR_DETAIL_REDIRECTOR_TIMEDOUT);
+            static const auto d = MakeNamedErrorDetail("REDIRECTOR_TIMEDOUT");
+            http->calloutsError(ERR_GATEWAY_FAILURE, d);
             debugs(85, DBG_IMPORTANT, "ERROR: URL rewrite helper: Timedout");
         }
         break;
@@ -1616,6 +1617,15 @@ ClientHttpRequest::sslBumpStart()
 
 #endif
 
+void
+ClientHttpRequest::updateError(const Error &error)
+{
+    if (request)
+        request->error.update(error);
+    else
+        al->updateError(error);
+}
+
 bool
 ClientHttpRequest::gotEnough() const
 {
@@ -1973,10 +1983,12 @@ ClientHttpRequest::noteAdaptationAnswer(const Adaptation::Answer &answer)
         handleAdaptationBlock(answer);
         break;
 
-    case Adaptation::Answer::akError:
-        handleAdaptationFailure(ERR_DETAIL_CLT_REQMOD_ABORT, !answer.final);
+    case Adaptation::Answer::akError: {
+        static const auto d = MakeNamedErrorDetail("CLT_REQMOD_ABORT");
+        handleAdaptationFailure(d, !answer.final);
         break;
     }
+    }
 }
 
 void
@@ -2027,7 +2039,8 @@ ClientHttpRequest::handleAdaptedHeader(Http::Message *msg)
 void
 ClientHttpRequest::handleAdaptationBlock(const Adaptation::Answer &answer)
 {
-    request->detailError(ERR_ACCESS_DENIED, ERR_DETAIL_REQMOD_BLOCK);
+    static const auto d = MakeNamedErrorDetail("REQMOD_BLOCK");
+    request->detailError(ERR_ACCESS_DENIED, d);
     AclMatchedName = answer.ruleId.termedBuf();
     assert(calloutContext);
     calloutContext->clientAccessCheckDone(ACCESS_DENIED);
@@ -2108,17 +2121,19 @@ ClientHttpRequest::noteBodyProducerAborted(BodyPipe::Pointer)
 
     debugs(85,3, HERE << "REQMOD body production failed");
     if (request_satisfaction_mode) { // too late to recover or serve an error
-        request->detailError(ERR_ICAP_FAILURE, ERR_DETAIL_CLT_REQMOD_RESP_BODY);
+        static const auto d = MakeNamedErrorDetail("CLT_REQMOD_RESP_BODY");
+        request->detailError(ERR_ICAP_FAILURE, d);
         const Comm::ConnectionPointer c = getConn()->clientConnection;
         Must(Comm::IsConnOpen(c));
         c->close(); // drastic, but we may be writing a response already
     } else {
-        handleAdaptationFailure(ERR_DETAIL_CLT_REQMOD_REQ_BODY);
+        static const auto d = MakeNamedErrorDetail("CLT_REQMOD_REQ_BODY");
+        handleAdaptationFailure(d);
     }
 }
 
 void
-ClientHttpRequest::handleAdaptationFailure(int errDetail, bool bypassable)
+ClientHttpRequest::handleAdaptationFailure(const ErrorDetail::Pointer &errDetail, bool bypassable)
 {
     debugs(85,3, HERE << "handleAdaptationFailure(" << bypassable << ")");
 
@@ -2164,7 +2179,7 @@ ClientHttpRequest::callException(const std::exception &ex)
 
 // XXX: modify and use with ClientRequestContext::clientAccessCheckDone too.
 void
-ClientHttpRequest::calloutsError(const err_type error, const int errDetail)
+ClientHttpRequest::calloutsError(const err_type error, const ErrorDetail::Pointer &errDetail)
 {
     // The original author of the code also wanted to pass an errno to
     // setReplyToError, but it seems unlikely that the errno reflects the
index 8b9a5484904f5faee228b9df06c153638896dfad..e52568f3a81680fb8aa3ff3b964053e863c50ce0 100644 (file)
@@ -65,12 +65,6 @@ public:
     ConnStateData * getConn() const {
         return (cbdataReferenceValid(conn_) ? conn_ : nullptr);
     }
-    void setConn(ConnStateData *aConn) {
-        if (conn_ != aConn) {
-            cbdataReferenceDone(conn_);
-            conn_ = cbdataReference(aConn);
-        }
-    }
 
     /// Initializes the current request with the virgin request.
     /// Call this method when the virgin request becomes known.
@@ -166,7 +160,10 @@ public:
     void setErrorUri(const char *errorUri);
 
     /// Build an error reply. For use with the callouts.
-    void calloutsError(const err_type error, const int errDetail);
+    void calloutsError(const err_type error, const ErrorDetail::Pointer &errDetail);
+
+    /// if necessary, stores new error information (if any)
+    void updateError(const Error &error);
 
 #if USE_ADAPTATION
     // AsyncJob virtual methods
@@ -215,7 +212,7 @@ public:
 private:
     /// Handles an adaptation client request failure.
     /// Bypasses the error if possible, or build an error reply.
-    void handleAdaptationFailure(int errDetail, bool bypassable = false);
+    void handleAdaptationFailure(const ErrorDetail::Pointer &errDetail, bool bypassable = false);
 
     // Adaptation::Initiator API
     virtual void noteAdaptationAnswer(const Adaptation::Answer &answer);
index 476dcbc60eb91cd965e8fe1ac4a8a9ef599f7d17..dfb13d053c310022ca1ad0c707170e45b1562f32 100644 (file)
@@ -14,7 +14,7 @@
 #include "comm/Connection.h"
 #include "comm/forward.h"
 #include "comm/Write.h"
-#include "err_detail_type.h"
+#include "error/Detail.h"
 #include "errorpage.h"
 #include "fd.h"
 #include "HttpHdrContRange.h"
@@ -869,15 +869,18 @@ Client::handledEarlyAdaptationAbort()
     if (entry->isEmpty()) {
         debugs(11,8, "adaptation failure with an empty entry: " << *entry);
         const auto err = new ErrorState(ERR_ICAP_FAILURE, Http::scInternalServerError, request.getRaw(), fwd->al);
-        err->detailError(ERR_DETAIL_ICAP_RESPMOD_EARLY);
+        static const auto d = MakeNamedErrorDetail("ICAP_RESPMOD_EARLY");
+        err->detailError(d);
         fwd->fail(err);
         fwd->dontRetry(true);
         abortAll("adaptation failure with an empty entry");
         return true; // handled
     }
 
-    if (request) // update logged info directly
-        request->detailError(ERR_ICAP_FAILURE, ERR_DETAIL_ICAP_RESPMOD_LATE);
+    if (request) { // update logged info directly
+        static const auto d = MakeNamedErrorDetail("ICAP_RESPMOD_LATE");
+        request->detailError(ERR_ICAP_FAILURE, d);
+    }
 
     return false; // the caller must handle
 }
@@ -892,8 +895,10 @@ Client::handleAdaptationBlocked(const Adaptation::Answer &answer)
         return;
 
     if (!entry->isEmpty()) { // too late to block (should not really happen)
-        if (request)
-            request->detailError(ERR_ICAP_FAILURE, ERR_DETAIL_RESPMOD_BLOCK_LATE);
+        if (request) {
+            static const auto d = MakeNamedErrorDetail("RESPMOD_BLOCK_LATE");
+            request->detailError(ERR_ICAP_FAILURE, d);
+        }
         abortAll("late adaptation block");
         return;
     }
@@ -906,7 +911,8 @@ Client::handleAdaptationBlocked(const Adaptation::Answer &answer)
         page_id = ERR_ACCESS_DENIED;
 
     const auto err = new ErrorState(page_id, Http::scForbidden, request.getRaw(), fwd->al);
-    err->detailError(ERR_DETAIL_RESPMOD_BLOCK_EARLY);
+    static const auto d = MakeNamedErrorDetail("RESPMOD_BLOCK_EARLY");
+    err->detailError(d);
     fwd->fail(err);
     fwd->dontRetry(true);
 
index 05a3ee7151435cc88d1eac85fae375037d190efc..bff53e7d4e9d4a76af920d9a05a7601b283c8fbd 100644 (file)
 #include "comm/TcpAcceptor.h"
 #include "comm/Write.h"
 #include "errorpage.h"
+#include "error/SysErrorDetail.h"
 #include "fd.h"
 #include "ftp/Parsing.h"
 #include "http/Stream.h"
 #include "ip/tools.h"
+#include "sbuf/SBuf.h"
+#include "sbuf/Stream.h"
 #include "SquidConfig.h"
 #include "SquidString.h"
 #include "StatCounters.h"
@@ -64,6 +67,20 @@ escapeIAC(const char *buf)
     return ret;
 }
 
+/* Ftp::ErrorDetail */
+
+SBuf
+Ftp::ErrorDetail::brief() const
+{
+    return ToSBuf("FTP_REPLY_CODE=", completionCode);
+}
+
+SBuf
+Ftp::ErrorDetail::verbose(const HttpRequest::Pointer &) const
+{
+    return ToSBuf("FTP reply with completion code ", completionCode);
+}
+
 /* Ftp::Channel */
 
 /// configures the channel with a descriptor and registers a close handler
@@ -285,7 +302,7 @@ Ftp::Client::failed(err_type error, int xerrno, ErrorState *err)
         ftperr->ftp.reply = xstrdup(reply);
 
     if (!err) {
-        fwd->request->detailError(error, xerrno);
+        fwd->request->detailError(error, SysErrorDetail::NewIfAny(xerrno));
         fwd->fail(ftperr);
         closeServer(); // we failed, so no serverComplete()
     }
index a76a5a0cd52b043843124e4edd008bbacdb61442..32bd4c0f823b7e8d974f89ca4208d9cf37cca9e7 100644 (file)
@@ -12,6 +12,7 @@
 #define SQUID_FTP_CLIENT_H
 
 #include "clients/Client.h"
+#include "error/Detail.h"
 
 class String;
 namespace Ftp
@@ -19,6 +20,24 @@ namespace Ftp
 
 extern const char *const crlf;
 
+/// Holds FTP server reply error code
+/// Squid needs to interpret internally FTP reply codes and respond with
+/// custom error (eg in the case of Ftp::Gateway), however still we need
+/// to log the exact FTP server error reply code as the reason of error.
+class ErrorDetail: public ::ErrorDetail {
+    MEMPROXY_CLASS(Ftp::ErrorDetail);
+
+public:
+    explicit ErrorDetail(const int code): completionCode(code) {}
+
+    /* ErrorDetail API */
+    virtual SBuf brief() const override;
+    virtual SBuf verbose(const HttpRequestPointer &) const override;
+
+private:
+    int completionCode; ///< FTP reply completion code
+};
+
 /// Common code for FTP server control and data channels.
 /// Does not own the channel descriptor, which is managed by Ftp::Client.
 class Channel
index 4aba041b13d25d514f5bbb95981105cb8ddc9ac2..e54d1f8fa5b9ad61cd45340ce44ad33cbc5bc316 100644 (file)
@@ -2433,8 +2433,8 @@ ftpFail(Ftp::Gateway *ftpState)
 
     Http::StatusCode sc = ftpState->failedHttpStatus(error_code);
     const auto ftperr = new ErrorState(error_code, sc, ftpState->fwd->request, ftpState->fwd->al);
-    ftpState->failed(error_code, code, ftperr);
-    ftperr->detailError(code);
+    ftpState->failed(error_code, 0, ftperr);
+    ftperr->detailError(new Ftp::ErrorDetail(code));
     HttpReply *newrep = ftperr->BuildHttpReply();
     delete ftperr;
 
@@ -2514,8 +2514,7 @@ ftpSendReply(Ftp::Gateway * ftpState)
     else
         err.ftp.reply = xstrdup("");
 
-    // TODO: interpret as FTP-specific error code
-    err.detailError(code);
+    err.detailError(new Ftp::ErrorDetail(code));
 
     ftpState->entry->replaceHttpReply(err.BuildHttpReply());
 
index 19967b3e6280c7f25be9f7700fb37ce8f05a35a5..191e9467bb56cd5be4fc927b198b944da8cc2641 100644 (file)
@@ -14,6 +14,7 @@
 #include "client_side.h"
 #include "clients/forward.h"
 #include "clients/FtpClient.h"
+#include "error/SysErrorDetail.h"
 #include "ftp/Elements.h"
 #include "ftp/Parsing.h"
 #include "http/Stream.h"
@@ -298,7 +299,7 @@ Ftp::Relay::failedErrorMessage(err_type error, int xerrno)
     const Http::StatusCode httpStatus = failedHttpStatus(error);
     HttpReply *const reply = createHttpReply(httpStatus);
     entry->replaceHttpReply(reply);
-    fwd->request->detailError(error, xerrno);
+    fwd->request->detailError(error, SysErrorDetail::NewIfAny(xerrno));
 }
 
 void
index 029347d720c643f943e6c8c738ec59195ea2524a..037010692de3e5200f7e1a6bbf88b06221203af2 100644 (file)
@@ -101,6 +101,7 @@ Comm::ReadNow(CommIoCbParams &params, SBuf &buf)
     } else if (retval == 0) { // remote closure (somewhat less) common
         // Note - read 0 == socket EOF, which is a valid read.
         params.flag = Comm::ENDFILE;
+        params.size = 0;
 
     } else if (retval < 0) { // connection errors are worst-case
         debugs(5, 3, params.conn << " Comm::COMM_ERROR: " << xstrerr(params.xerrno));
@@ -108,6 +109,7 @@ Comm::ReadNow(CommIoCbParams &params, SBuf &buf)
             params.flag =  Comm::INPROGRESS;
         else
             params.flag =  Comm::COMM_ERROR;
+        params.size = 0;
     }
 
     return params.flag;
diff --git a/src/err_detail_type.h b/src/err_detail_type.h
deleted file mode 100644 (file)
index 948540d..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
- *
- * Squid software is distributed under GPLv2+ license and includes
- * contributions from numerous individuals and organizations.
- * Please see the COPYING and CONTRIBUTORS files for details.
- */
-
-#ifndef _SQUID_ERR_DETAIL_H
-#define  _SQUID_ERR_DETAIL_H
-
-typedef enum {
-    ERR_DETAIL_NONE,
-    ERR_DETAIL_START = 100000, // to avoid clashes with most OS error numbers
-    ERR_DETAIL_REDIRECTOR_TIMEDOUT = ERR_DETAIL_START, // External redirector request timed-out
-    ERR_DETAIL_CLT_REQMOD_ABORT, // client-facing code detected transaction abort
-    ERR_DETAIL_CLT_REQMOD_REQ_BODY, // client-facing code detected REQMOD request body adaptation failure
-    ERR_DETAIL_CLT_REQMOD_RESP_BODY, // client-facing code detected REQMOD satisfaction reply body failure
-    ERR_DETAIL_SRV_REQMOD_REQ_BODY, // server-facing code detected REQMOD request body abort
-    ERR_DETAIL_ICAP_RESPMOD_EARLY, // RESPMOD failed w/o store entry
-    ERR_DETAIL_ICAP_RESPMOD_LATE,  // RESPMOD failed with a store entry
-    ERR_DETAIL_REQMOD_BLOCK, // REQMOD denied client access
-    ERR_DETAIL_RESPMOD_BLOCK_EARLY, // RESPMOD denied client access to HTTP response, before any part of the response was sent
-    ERR_DETAIL_RESPMOD_BLOCK_LATE, // RESPMOD denied client access to HTTP response, after [a part of] the response was sent
-    ERR_DETAIL_ICAP_XACT_START, // transaction start failure
-    ERR_DETAIL_ICAP_XACT_SSL_START, // transaction start failure
-    ERR_DETAIL_ICAP_XACT_BODY_CONSUMER_ABORT, // transaction body consumer gone
-    ERR_DETAIL_ICAP_INIT_GONE, // initiator gone
-    ERR_DETAIL_ICAP_XACT_CLOSE, // ICAP connection closed unexpectedly
-    ERR_DETAIL_ICAP_XACT_OTHER, // other ICAP transaction errors
-    ERR_DETAIL_EXCEPTION_OTHER, //other errors ( eg std C++ lib errors)
-    ERR_DETAIL_MAX,
-    ERR_DETAIL_EXCEPTION_START = 110000 // offset for exception ID details
-} err_detail_type;
-
-extern const char *err_detail_type_str[];
-
-inline
-const char *errorDetailName(int errDetailId)
-{
-    if (errDetailId < ERR_DETAIL_START)
-        return "SYSERR";
-
-    if (errDetailId < ERR_DETAIL_MAX)
-        return err_detail_type_str[errDetailId-ERR_DETAIL_START+2];
-
-    if (errDetailId >=ERR_DETAIL_EXCEPTION_START)
-        return "EXCEPTION";
-
-    return "UNKNOWN";
-}
-
-#endif
-
diff --git a/src/error/Detail.cc b/src/error/Detail.cc
new file mode 100644 (file)
index 0000000..4e30318
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#include "squid.h"
+#include "error/Detail.h"
+#include "HttpRequest.h"
+#include "sbuf/SBuf.h"
+#include "sbuf/Stream.h"
+
+/// details an error by tying it to a uniquely named circumstance
+class NamedErrorDetail: public ErrorDetail
+{
+public:
+    // convert from c-string to SBuf to simplify creation and optimize usage
+    /// \param aName must not contain characters that require quoting in access logs or HTML
+    explicit NamedErrorDetail(const char *aName): name(aName) {}
+
+    /* ErrorDetail API */
+    virtual SBuf brief() const override { return name; }
+    virtual SBuf verbose(const HttpRequestPointer &) const override { return name; }
+
+private:
+    /// distinguishes us from all other NamedErrorDetail objects
+    SBuf name;
+};
+
+/* ErrorDetail */
+
+std::ostream &
+operator <<(std::ostream &os, const ErrorDetail &detail)
+{
+    os << detail.brief();
+    return os;
+}
+
+std::ostream &
+operator <<(std::ostream &os, const ErrorDetail::Pointer &detail)
+{
+    if (detail)
+        os << *detail;
+    else
+        os << "[no details]";
+    return os;
+}
+
+/* NamedErrorDetail */
+
+ErrorDetail::Pointer
+MakeNamedErrorDetail(const char *name)
+{
+    return new NamedErrorDetail(name);
+}
+
diff --git a/src/error/Detail.h b/src/error/Detail.h
new file mode 100644 (file)
index 0000000..84cb879
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#ifndef _SQUID_SRC_ERROR_DETAIL_H
+#define _SQUID_SRC_ERROR_DETAIL_H
+
+#include "base/Here.h"
+#include "base/RefCount.h"
+#include "error/forward.h"
+#include "http/forward.h"
+#include "mem/forward.h"
+#include "sbuf/forward.h"
+
+/// interface for supplying additional information about a transaction failure
+class ErrorDetail: public RefCountable
+{
+public:
+    using Pointer = ErrorDetailPointer;
+
+    virtual ~ErrorDetail() {}
+
+    /// \returns a single "token" summarizing available details
+    /// suitable as an access.log field and similar output processed by programs
+    virtual SBuf brief() const = 0;
+
+    /// \returns all available details; may be customized for the given request
+    /// suitable for error pages and other output meant for human consumption
+    virtual SBuf verbose(const HttpRequestPointer &) const = 0;
+};
+
+/// creates a new NamedErrorDetail object with a unique name
+/// \see NamedErrorDetail::Name for naming restrictions
+ErrorDetail::Pointer MakeNamedErrorDetail(const char *name);
+
+/// dump the given ErrorDetail (for debugging)
+std::ostream &operator <<(std::ostream &os, const ErrorDetail &);
+
+/// dump the given ErrorDetail pointer which may be nil (for debugging)
+std::ostream &operator <<(std::ostream &os, const ErrorDetail::Pointer &);
+
+#endif /* _SQUID_SRC_ERROR_DETAIL_H */
+
diff --git a/src/error/Error.cc b/src/error/Error.cc
new file mode 100644 (file)
index 0000000..3ad637d
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+/* DEBUG: section 04    Error Management */
+
+#include "squid.h"
+#include "Debug.h"
+#include "error/Error.h"
+
+void
+Error::update(const Error &recent)
+{
+    if (*this)
+        debugs(4, 5, "old: " << *this);
+    if (!recent)
+        return; // no changes
+    debugs(4, 3, "recent: " << recent);
+    // checking category and detail separately may cause inconsistency, but
+    // may result in more details available if they only become available later
+    if (category == ERR_NONE)
+        category = recent.category; // may still be ERR_NONE
+    if (!detail)
+        detail = recent.detail; // may still be nil
+}
+
+std::ostream &
+operator <<(std::ostream &os, const Error &error)
+{
+    os << errorTypeName(error.category);
+    if (error.detail)
+        os << '/' << *error.detail;
+    return os;
+}
+
diff --git a/src/error/Error.h b/src/error/Error.h
new file mode 100644 (file)
index 0000000..15ce486
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#ifndef _SQUID_SRC_ERROR_ERROR_H
+#define _SQUID_SRC_ERROR_ERROR_H
+
+#include "error/forward.h"
+#include "error/Detail.h"
+
+#include <iosfwd>
+
+/// a transaction problem
+class Error {
+public:
+    Error() = default;
+    Error(const err_type c): category(c) {} ///< support implicit conversions
+    Error(const err_type c, const ErrorDetailPointer &d): category(c), detail(d) {}
+
+    explicit operator bool() const { return category != ERR_NONE; }
+
+    /// if necessary, stores the given error information (if any)
+    void update(const Error &);
+
+    /// convenience wrapper for update(Error)
+    void update(const err_type c, const ErrorDetailPointer &d) { update(Error(c, d)); }
+
+    /// switch to the default "no error information" state
+    void clear() { *this = Error(); }
+
+    err_type category = ERR_NONE; ///< primary error classification (or ERR_NONE)
+    ErrorDetailPointer detail; ///< additional details about the error
+};
+
+extern const char *err_type_str[];
+
+inline
+err_type
+errorTypeByName(const char *name)
+{
+    for (int i = 0; i < ERR_MAX; ++i)
+        if (strcmp(name, err_type_str[i]) == 0)
+            return (err_type)i;
+    return ERR_MAX;
+}
+
+inline
+const char *
+errorTypeName(err_type err)
+{
+    if (err < ERR_NONE || err >= ERR_MAX)
+        return "UNKNOWN";
+    return err_type_str[err];
+}
+
+std::ostream &operator <<(std::ostream &os, const Error &error);
+
+#endif /* _SQUID_SRC_ERROR_ERROR_H */
+
diff --git a/src/error/ExceptionErrorDetail.h b/src/error/ExceptionErrorDetail.h
new file mode 100644 (file)
index 0000000..e802bda
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#ifndef _SQUID_SRC_ERROR_EXCEPTIONERRORDETAIL_H
+#define _SQUID_SRC_ERROR_EXCEPTIONERRORDETAIL_H
+
+#include "error/Detail.h"
+#include "sbuf/SBuf.h"
+#include "sbuf/Stream.h"
+
+/// offset for exception ID details, for backward compatibility
+#define SQUID_EXCEPTION_START_BASE 110000
+
+/// Details a failure reported via a C++ exception. Stores exception ID which
+/// scripts/calc-must-ids.sh can map to a relevant source code location.
+class ExceptionErrorDetail: public ErrorDetail
+{
+    MEMPROXY_CLASS(ExceptionErrorDetail);
+
+public:
+    explicit ExceptionErrorDetail(const SourceLocationId id): exceptionId(SQUID_EXCEPTION_START_BASE + id) {}
+
+    /* ErrorDetail API */
+    virtual SBuf brief() const override {
+        return ToSBuf("exception=", std::hex, exceptionId);
+    }
+
+    virtual SBuf verbose(const HttpRequestPointer &) const override {
+        return ToSBuf("Exception (ID=", std::hex, exceptionId, ')');
+    }
+
+private:
+    SourceLocationId exceptionId; ///< identifies the thrower or catcher
+ };
+
+#endif /* _SQUID_SRC_ERROR_EXCEPTIONERRORDETAIL_H */
+
diff --git a/src/error/Makefile.am b/src/error/Makefile.am
new file mode 100644 (file)
index 0000000..3510531
--- /dev/null
@@ -0,0 +1,33 @@
+## Copyright (C) 1996-2020 The Squid Software Foundation and contributors
+##
+## Squid software is distributed under GPLv2+ license and includes
+## contributions from numerous individuals and organizations.
+## Please see the COPYING and CONTRIBUTORS files for details.
+##
+
+include $(top_srcdir)/src/Common.am
+include $(top_srcdir)/src/TestHeaders.am
+
+categories.cc: forward.h $(top_srcdir)/src/mk-string-arrays.awk
+       $(AWK) -f $(top_srcdir)/src/mk-string-arrays.awk ifile=error/forward.h < $(srcdir)/forward.h > $@ || ($(RM) -f $@ && exit 1)
+
+BUILT_SOURCES = \
+       categories.cc
+
+noinst_LTLIBRARIES = liberror.la
+
+liberror_la_SOURCES = \
+       forward.h \
+       Error.cc \
+       Error.h \
+       Detail.cc \
+       Detail.h \
+       SysErrorDetail.h \
+       ExceptionErrorDetail.h
+
+nodist_liberror_la_SOURCES = \
+       $(BUILT_SOURCES)
+
+CLEANFILES += \
+       $(BUILT_SOURCES)
+
diff --git a/src/error/SysErrorDetail.h b/src/error/SysErrorDetail.h
new file mode 100644 (file)
index 0000000..765d3f2
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#ifndef _SQUID_SRC_ERROR_SYSERRORDETAIL_H
+#define _SQUID_SRC_ERROR_SYSERRORDETAIL_H
+
+#include "error/Detail.h"
+#include "sbuf/SBuf.h"
+#include "sbuf/Stream.h"
+
+/// system call failure detail based on standard errno(3)/strerror(3) APIs
+class SysErrorDetail: public ErrorDetail
+{
+    MEMPROXY_CLASS(SysErrorDetail);
+
+public:
+    /// \returns a pointer to a SysErrorDetail instance (or nil for lost errnos)
+    static ErrorDetail::Pointer NewIfAny(const int errorNo)
+    {
+        // we could optimize by caching results for (frequently used?) errnos
+        return errorNo ? new SysErrorDetail(errorNo) : nullptr;
+    }
+
+    static SBuf Brief(int errorNo) {
+        return SysErrorDetail(errorNo).brief();
+    }
+
+    /* ErrorDetail API */
+    virtual SBuf brief() const override {
+        return ToSBuf("errno=", errorNo);
+    }
+
+    virtual SBuf verbose(const HttpRequestPointer &) const override {
+        return SBuf(strerror(errorNo));
+    }
+
+private:
+    // hidden by NewIfAny() to avoid creating SysErrorDetail from zero errno
+    explicit SysErrorDetail(const int anErrorNo): errorNo(anErrorNo) {}
+
+    int errorNo; ///< errno(3) set by the last failed system call or equivalent
+};
+
+#endif /* _SQUID_SRC_ERROR_SYSERRORDETAIL_H */
+
similarity index 84%
rename from src/err_type.h
rename to src/error/forward.h
index d84ffe51f48bc707067c762050aeb0c6e992c73d..981fabf95bd51d83c159e25450478efd669d08ae 100644 (file)
@@ -6,8 +6,10 @@
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
-#ifndef _SQUID_ERR_TYPE_H
-#define _SQUID_ERR_TYPE_H
+#ifndef _SQUID_SRC_ERROR_FORWARD_H
+#define _SQUID_SRC_ERROR_FORWARD_H
+
+#include "base/forward.h"
 
 typedef enum {
     ERR_NONE,
@@ -74,6 +76,7 @@ typedef enum {
     //       a log warning if the files are missing
     TCP_RESET,                  // Send TCP RST packet instead of error page
 
+    ERR_CLIENT_GONE, // No client to send the error page to
     ERR_SECURE_ACCEPT_FAIL, // Rejects the SSL connection instead of error page
     ERR_REQUEST_START_TIMEOUT, // Aborts the connection instead of error page
     ERR_REQUEST_PARSE_TIMEOUT, // Aborts the connection instead of error page
@@ -85,26 +88,10 @@ typedef enum {
     ERR_MAX
 } err_type;
 
-extern const char *err_type_str[];
-
-inline
-err_type
-errorTypeByName(const char *name)
-{
-    for (int i = 0; i < ERR_MAX; ++i)
-        if (strcmp(name, err_type_str[i]) == 0)
-            return (err_type)i;
-    return ERR_MAX;
-}
-
-inline
-const char *
-errorTypeName(err_type err)
-{
-    if (err < ERR_NONE || err >= ERR_MAX)
-        return "UNKNOWN";
-    return err_type_str[err];
-}
-
-#endif /* _SQUID_ERR_TYPE_H */
+class Error;
+class ErrorDetail;
+
+typedef RefCount<ErrorDetail> ErrorDetailPointer;
+
+#endif /* _SQUID_SRC_ERROR_FORWARD_H */
 
index b3e98154f94f4b024c1572df9e74d0b81e339623..50706788e9b7d9dc2950fd93ec62d5bcf3ea1f90 100644 (file)
@@ -14,7 +14,8 @@
 #include "clients/forward.h"
 #include "comm/Connection.h"
 #include "comm/Write.h"
-#include "err_detail_type.h"
+#include "error/Detail.h"
+#include "error/SysErrorDetail.h"
 #include "errorpage.h"
 #include "fde.h"
 #include "format/Format.h"
@@ -168,6 +169,10 @@ error_hard_text[] = {
         TCP_RESET,
         "reset"
     },
+    {
+        ERR_CLIENT_GONE,
+        "unexpected client disconnect"
+    },
     {
         ERR_SECURE_ACCEPT_FAIL,
         "secure accept fail"
@@ -796,9 +801,6 @@ ErrorState::~ErrorState()
     if (err_language != Config.errorDefaultLanguage)
 #endif
         safe_free(err_language);
-#if USE_OPENSSL
-    delete detail;
-#endif
 }
 
 int
@@ -962,18 +964,13 @@ ErrorState::compileLegacyCode(Build &build)
     case 'D':
         if (!build.allowRecursion)
             p = "%D";  // if recursion is not allowed, do not convert
-#if USE_OPENSSL
-        // currently only SSL error details implemented
         else if (detail) {
-            detail->useRequest(request.getRaw());
-            const String &errDetail = detail->toString();
-            if (errDetail.size() > 0) {
-                const auto compiledDetail = compileBody(errDetail.termedBuf(), false);
-                mb.append(compiledDetail.rawContent(), compiledDetail.length());
-                do_quote = 0;
-            }
+            auto rawDetail = detail->verbose(request);
+            // XXX: Performance regression. c_str() reallocates
+            const auto compiledDetail = compileBody(rawDetail.c_str(), false);
+            mb.append(compiledDetail.rawContent(), compiledDetail.length());
+            do_quote = 0;
         }
-#endif
         if (!mb.contentSize())
             mb.append("[No Error Detail]", 17);
         break;
@@ -1204,13 +1201,12 @@ ErrorState::compileLegacyCode(Build &build)
         break;
 
     case 'x':
-#if USE_OPENSSL
-        if (detail)
-            mb.appendf("%s", detail->errorName());
-        else
-#endif
-            if (!building_deny_info_url)
-                p = "[Unknown Error Code]";
+        if (detail) {
+            const auto brief = detail->brief();
+            mb.append(brief.rawContent(), brief.length());
+        } else if (!building_deny_info_url) {
+            p = "[Unknown Error Code]";
+        }
         break;
 
     case 'z':
@@ -1351,17 +1347,10 @@ ErrorState::BuildHttpReply()
     // Make sure error codes get back to the client side for logging and
     // error tracking.
     if (request) {
-        int edc = ERR_DETAIL_NONE; // error detail code
-#if USE_OPENSSL
         if (detail)
-            edc = detail->errorNo();
+            request->detailError(type, detail);
         else
-#endif
-            if (detailCode)
-                edc = detailCode;
-            else
-                edc = xerrno;
-        request->detailError(type, edc);
+            request->detailError(type, SysErrorDetail::NewIfAny(xerrno));
     }
 
     return rep;
index f8cec6ccdaf9f348c49855544e9168cb2c38ebef..18c3dfa4c6dfae2ede277e9af34a0f7d2f5a5e05 100644 (file)
@@ -13,8 +13,8 @@
 
 #include "cbdata.h"
 #include "comm/forward.h"
-#include "err_detail_type.h"
-#include "err_type.h"
+#include "error/Detail.h"
+#include "error/forward.h"
 #include "http/forward.h"
 #include "http/StatusCode.h"
 #include "ip/Address.h"
@@ -23,9 +23,6 @@
 #include "SquidString.h"
 /* auth/UserRequest.h is empty unless USE_AUTH is defined */
 #include "auth/UserRequest.h"
-#if USE_OPENSSL
-#include "ssl/ErrorDetail.h"
-#endif
 
 /// error page callback
 typedef void ERCB(int fd, void *, size_t);
@@ -109,7 +106,7 @@ public:
     HttpReply *BuildHttpReply(void);
 
     /// set error type-specific detail code
-    void detailError(int dCode) {detailCode = dCode;}
+    void detailError(const ErrorDetail::Pointer &dCode) { detail = dCode; }
 
     /// ensures that a future BuildHttpReply() is likely to succeed
     void validate();
@@ -200,12 +197,11 @@ public:
 
     AccessLogEntryPointer ale; ///< transaction details (or nil)
 
-#if USE_OPENSSL
-    Ssl::ErrorDetail *detail = nullptr;
-#endif
+    // TODO: Replace type, xerrno and detail with Error while adding a virtual
+    // Error::Detail::sysError() method to extract errno in detailError().
     /// type-specific detail about the transaction error;
-    /// overwrites xerrno; overwritten by detail, if any.
-    int detailCode = ERR_DETAIL_NONE;
+    /// overwrites xerrno;
+    ErrorDetail::Pointer detail;
 
     HttpReplyPointer response_;
 
index a3dae30b2ca343d51324352a8a503d8187ef8655..4326dbe8a836e7a703380a9501b2c54e72204ae3 100644 (file)
@@ -10,7 +10,7 @@
 #define SQUID_ESICONTEXT_H
 
 #include "clientStream.h"
-#include "err_type.h"
+#include "error/forward.h"
 #include "esi/Element.h"
 #include "esi/Esi.h"
 #include "esi/Parser.h"
index a49f0e86659008f103deeea6d4e8fefcd33b8ce0..f5bd47b1ae802b35a43e54a50bd92f2fe99db530 100644 (file)
@@ -11,7 +11,7 @@
 #include "base64.h"
 #include "client_side.h"
 #include "comm/Connection.h"
-#include "err_detail_type.h"
+#include "error/Detail.h"
 #include "errorpage.h"
 #include "fde.h"
 #include "format/Format.h"
@@ -993,29 +993,17 @@ Format::Format::assemble(MemBuf &mb, const AccessLogEntry::Pointer &al, int logS
             break;
 
         case LFT_SQUID_ERROR:
-            if (al->request && al->request->errType != ERR_NONE)
-                out = errorPageName(al->request->errType);
+            if (const auto error = al->error())
+                out = errorPageName(error->category);
             break;
 
         case LFT_SQUID_ERROR_DETAIL:
-#if USE_OPENSSL
-            if (al->request && al->request->errType == ERR_SECURE_CONNECT_FAIL) {
-                out = Ssl::GetErrorName(al->request->errDetail, true);
-            } else
-#endif
-                if (al->request && al->request->errDetail != ERR_DETAIL_NONE) {
-                    if (al->request->errDetail > ERR_DETAIL_START && al->request->errDetail < ERR_DETAIL_MAX)
-                        out = errorDetailName(al->request->errDetail);
-                    else {
-                        if (al->request->errDetail >= ERR_DETAIL_EXCEPTION_START)
-                            sb.appendf("%s=0x%X",
-                                       errorDetailName(al->request->errDetail), (uint32_t) al->request->errDetail);
-                        else
-                            sb.appendf("%s=%d",
-                                       errorDetailName(al->request->errDetail), al->request->errDetail);
-                        out = sb.c_str();
-                    }
+            if (const auto error = al->error()) {
+                if (const auto detail = error->detail) {
+                    sb = detail->brief();
+                    out = sb.c_str();
                 }
+            }
             break;
 
         case LFT_SQUID_HIERARCHY:
index 09f9759322173e805e35062f1a9e0a01b9298f16..04ecd8848b0002303fbb9480d7e503a7c856071a 100644 (file)
@@ -24,7 +24,7 @@
 #include "comm/Read.h"
 #include "comm/Write.h"
 #include "CommRead.h"
-#include "err_detail_type.h"
+#include "error/Detail.h"
 #include "errorpage.h"
 #include "fd.h"
 #include "fde.h"
@@ -2630,7 +2630,8 @@ HttpStateData::handleRequestBodyProducerAborted()
         // should not matter because either client-side will provide its own or
         // there will be no response at all (e.g., if the the client has left).
         const auto err = new ErrorState(ERR_ICAP_FAILURE, Http::scInternalServerError, fwd->request, fwd->al);
-        err->detailError(ERR_DETAIL_SRV_REQMOD_REQ_BODY);
+        static const auto d = MakeNamedErrorDetail("SRV_REQMOD_REQ_BODY");
+        err->detailError(d);
         fwd->fail(err);
     }
 
index 0e786d483f188333233aef5387a64343293e67d0..6aa04d41aada623a897c5d2dcd7af648d183d3c1 100644 (file)
@@ -54,7 +54,7 @@ Http::Stream::registerWithConn()
     assert(!connRegistered_);
     assert(getConn());
     connRegistered_ = true;
-    getConn()->pipeline.add(Http::StreamPointer(this));
+    getConn()->add(this);
 }
 
 bool
@@ -560,12 +560,11 @@ Http::Stream::getConn() const
 
 /// remembers the abnormal connection termination for logging purposes
 void
-Http::Stream::noteIoError(const int xerrno)
+Http::Stream::noteIoError(const Error &error, const LogTagsErrors &lte)
 {
     if (http) {
-        http->logType.err.timedout = (xerrno == ETIMEDOUT);
-        // aborted even if xerrno is zero (which means read abort/eof)
-        http->logType.err.aborted = (xerrno != ETIMEDOUT);
+        http->updateError(error);
+        http->logType.err.update(lte);
     }
 }
 
index 7c543e01eb6360330755d4135d85d08212e30e6a..7de6c0a5c2656473db9e337a62ac0dd6df5dbd8e 100644 (file)
@@ -116,7 +116,7 @@ public:
     ConnStateData *getConn() const;
 
     /// update state to reflect I/O error
-    void noteIoError(const int xerrno);
+    void noteIoError(const Error &, const LogTagsErrors &);
 
     /// cleanup when the transaction has finished. may destroy 'this'
     void finished();
index 38887dd10eb7cd67e7e2e999152f85bbe4085058..00b73467b5de8fbf5f8e5c1e8c71c1a505e580c5 100644 (file)
@@ -15,7 +15,7 @@
 #include "adaptation/Config.h"
 #endif
 #include "CachePeer.h"
-#include "err_detail_type.h"
+#include "error/Detail.h"
 #include "errorpage.h"
 #include "format/Token.h"
 #include "globals.h"
index fc7eb02ceea1662f94b90930935b73e52aff74db..733d9b27c66c4a6c5f6b2904cfd5381b85aeb2f5 100644 (file)
@@ -14,5 +14,8 @@
 class AccessLogEntry;
 typedef RefCount<AccessLogEntry> AccessLogEntryPointer;
 
+class LogTags;
+class LogTagsErrors;
+
 #endif /* SQUID_FORMAT_FORWARD_H */
 
index 9aac7ce19fae85ba6913c2d3c1b7bcb5179e4bd2..98a6da75c6e430a9cbac342255d092d9238aaaac 100644 (file)
@@ -74,7 +74,8 @@ codeSkip == 1         { next }
 
 END {
        if (sbuf) print "#include \"sbuf/SBuf.h\""
-       print "#include \"" nspath type ".h\""
+       if (ifile != "") print "#include \"" ifile "\""
+        else print "#include \"" nspath type ".h\""
 
        # if namespace is not empty ??
        if (namespace) print "namespace " namespace
diff --git a/src/security/ErrorDetail.cc b/src/security/ErrorDetail.cc
new file mode 100644 (file)
index 0000000..7bec409
--- /dev/null
@@ -0,0 +1,747 @@
+/*
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#include "squid.h"
+#include "error/SysErrorDetail.h"
+#include "html_quote.h"
+#include "sbuf/SBuf.h"
+#include "sbuf/Stream.h"
+#include "security/ErrorDetail.h"
+#include "security/forward.h"
+#include "security/Io.h"
+#include "util.h"
+
+#if USE_OPENSSL
+#include "ssl/ErrorDetailManager.h"
+#elif USE_GNUTLS
+#if HAVE_GNUTLS_GNUTLS_H
+#include <gnutls/gnutls.h>
+#endif
+#endif
+#include <map>
+
+namespace Security {
+
+// we use std::map to optimize search; TODO: Use std::unordered_map instead?
+typedef std::map<ErrorCode, const char *> ErrorCodeNames;
+static const ErrorCodeNames TheErrorCodeNames = {
+    {   SQUID_TLS_ERR_ACCEPT,
+        "SQUID_TLS_ERR_ACCEPT"
+    },
+    {   SQUID_TLS_ERR_CONNECT,
+        "SQUID_TLS_ERR_CONNECT"
+    },
+    {   SQUID_X509_V_ERR_INFINITE_VALIDATION,
+        "SQUID_X509_V_ERR_INFINITE_VALIDATION"
+    },
+    {   SQUID_X509_V_ERR_CERT_CHANGE,
+        "SQUID_X509_V_ERR_CERT_CHANGE"
+    },
+    {   SQUID_X509_V_ERR_DOMAIN_MISMATCH,
+        "SQUID_X509_V_ERR_DOMAIN_MISMATCH"
+    },
+#if USE_OPENSSL
+    {   X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT,
+        "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT"
+    },
+    {   X509_V_ERR_UNABLE_TO_GET_CRL,
+        "X509_V_ERR_UNABLE_TO_GET_CRL"
+    },
+    {   X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE,
+        "X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE"
+    },
+    {   X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE,
+        "X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE"
+    },
+    {   X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY,
+        "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY"
+    },
+    {   X509_V_ERR_CERT_SIGNATURE_FAILURE,
+        "X509_V_ERR_CERT_SIGNATURE_FAILURE"
+    },
+    {   X509_V_ERR_CRL_SIGNATURE_FAILURE,
+        "X509_V_ERR_CRL_SIGNATURE_FAILURE"
+    },
+    {   X509_V_ERR_CERT_NOT_YET_VALID,
+        "X509_V_ERR_CERT_NOT_YET_VALID"
+    },
+    {   X509_V_ERR_CERT_HAS_EXPIRED,
+        "X509_V_ERR_CERT_HAS_EXPIRED"
+    },
+    {   X509_V_ERR_CRL_NOT_YET_VALID,
+        "X509_V_ERR_CRL_NOT_YET_VALID"
+    },
+    {   X509_V_ERR_CRL_HAS_EXPIRED,
+        "X509_V_ERR_CRL_HAS_EXPIRED"
+    },
+    {   X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD,
+        "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD"
+    },
+    {   X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD,
+        "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD"
+    },
+    {   X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD,
+        "X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD"
+    },
+    {   X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD,
+        "X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD"
+    },
+    {   X509_V_ERR_OUT_OF_MEM,
+        "X509_V_ERR_OUT_OF_MEM"
+    },
+    {   X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,
+        "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
+    },
+    {   X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN,
+        "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN"
+    },
+    {   X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
+        "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
+    },
+    {   X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE,
+        "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
+    },
+    {   X509_V_ERR_CERT_CHAIN_TOO_LONG,
+        "X509_V_ERR_CERT_CHAIN_TOO_LONG"
+    },
+    {   X509_V_ERR_CERT_REVOKED,
+        "X509_V_ERR_CERT_REVOKED"
+    },
+    {   X509_V_ERR_INVALID_CA,
+        "X509_V_ERR_INVALID_CA"
+    },
+    {   X509_V_ERR_PATH_LENGTH_EXCEEDED,
+        "X509_V_ERR_PATH_LENGTH_EXCEEDED"
+    },
+    {   X509_V_ERR_INVALID_PURPOSE,
+        "X509_V_ERR_INVALID_PURPOSE"
+    },
+    {   X509_V_ERR_CERT_UNTRUSTED,
+        "X509_V_ERR_CERT_UNTRUSTED"
+    },
+    {   X509_V_ERR_CERT_REJECTED,
+        "X509_V_ERR_CERT_REJECTED"
+    },
+    {   X509_V_ERR_SUBJECT_ISSUER_MISMATCH,
+        "X509_V_ERR_SUBJECT_ISSUER_MISMATCH"
+    },
+    {   X509_V_ERR_AKID_SKID_MISMATCH,
+        "X509_V_ERR_AKID_SKID_MISMATCH"
+    },
+    {   X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH,
+        "X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH"
+    },
+    {   X509_V_ERR_KEYUSAGE_NO_CERTSIGN,
+        "X509_V_ERR_KEYUSAGE_NO_CERTSIGN"
+    },
+#if defined(X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER)
+    {
+        X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER, // 33
+        "X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER"
+    },
+#endif
+#if defined(X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION)
+    {
+        X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION, // 34
+        "X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION"
+    },
+#endif
+#if defined(X509_V_ERR_KEYUSAGE_NO_CRL_SIGN)
+    {
+        X509_V_ERR_KEYUSAGE_NO_CRL_SIGN, // 35
+        "X509_V_ERR_KEYUSAGE_NO_CRL_SIGN"
+    },
+#endif
+#if defined(X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION)
+    {
+        X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION, // 36
+        "X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION"
+    },
+#endif
+#if defined(X509_V_ERR_INVALID_NON_CA)
+    {
+        X509_V_ERR_INVALID_NON_CA, // 37
+        "X509_V_ERR_INVALID_NON_CA"
+    },
+#endif
+#if defined(X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED)
+    {
+        X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED, // 38
+        "X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED"
+    },
+#endif
+#if defined(X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE)
+    {
+        X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE, // 39
+        "X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE"
+    },
+#endif
+#if defined(X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED)
+    {
+        X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED, // 40
+        "X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED"
+    },
+#endif
+#if defined(X509_V_ERR_INVALID_EXTENSION)
+    {
+        X509_V_ERR_INVALID_EXTENSION, // 41
+        "X509_V_ERR_INVALID_EXTENSION"
+    },
+#endif
+#if defined(X509_V_ERR_INVALID_POLICY_EXTENSION)
+    {
+        X509_V_ERR_INVALID_POLICY_EXTENSION, // 42
+        "X509_V_ERR_INVALID_POLICY_EXTENSION"
+    },
+#endif
+#if defined(X509_V_ERR_NO_EXPLICIT_POLICY)
+    {
+        X509_V_ERR_NO_EXPLICIT_POLICY, // 43
+        "X509_V_ERR_NO_EXPLICIT_POLICY"
+    },
+#endif
+#if defined(X509_V_ERR_DIFFERENT_CRL_SCOPE)
+    {
+        X509_V_ERR_DIFFERENT_CRL_SCOPE, // 44
+        "X509_V_ERR_DIFFERENT_CRL_SCOPE"
+    },
+#endif
+#if defined(X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE)
+    {
+        X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE, // 45
+        "X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE"
+    },
+#endif
+#if defined(X509_V_ERR_UNNESTED_RESOURCE)
+    {
+        X509_V_ERR_UNNESTED_RESOURCE, // 46
+        "X509_V_ERR_UNNESTED_RESOURCE"
+    },
+#endif
+#if defined(X509_V_ERR_PERMITTED_VIOLATION)
+    {
+        X509_V_ERR_PERMITTED_VIOLATION, // 47
+        "X509_V_ERR_PERMITTED_VIOLATION"
+    },
+#endif
+#if defined(X509_V_ERR_EXCLUDED_VIOLATION)
+    {
+        X509_V_ERR_EXCLUDED_VIOLATION, // 48
+        "X509_V_ERR_EXCLUDED_VIOLATION"
+    },
+#endif
+#if defined(X509_V_ERR_SUBTREE_MINMAX)
+    {
+        X509_V_ERR_SUBTREE_MINMAX, // 49
+        "X509_V_ERR_SUBTREE_MINMAX"
+    },
+#endif
+    {   X509_V_ERR_APPLICATION_VERIFICATION, // 50
+        "X509_V_ERR_APPLICATION_VERIFICATION"
+    },
+#if defined(X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE)
+    {
+        X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE, // 51
+        "X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE"
+    },
+#endif
+#if defined(X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX)
+    {
+        X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX, // 52
+        "X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX"
+    },
+#endif
+#if defined(X509_V_ERR_UNSUPPORTED_NAME_SYNTAX)
+    {
+        X509_V_ERR_UNSUPPORTED_NAME_SYNTAX, // 53
+        "X509_V_ERR_UNSUPPORTED_NAME_SYNTAX"
+    },
+#endif
+#if defined(X509_V_ERR_CRL_PATH_VALIDATION_ERROR)
+    {
+        X509_V_ERR_CRL_PATH_VALIDATION_ERROR, // 54
+        "X509_V_ERR_CRL_PATH_VALIDATION_ERROR"
+    },
+#endif
+#if defined(X509_V_ERR_PATH_LOOP)
+    {
+        X509_V_ERR_PATH_LOOP, // 55
+        "X509_V_ERR_PATH_LOOP"
+    },
+#endif
+#if defined(X509_V_ERR_SUITE_B_INVALID_VERSION)
+    {
+        X509_V_ERR_SUITE_B_INVALID_VERSION, // 56
+        "X509_V_ERR_SUITE_B_INVALID_VERSION"
+    },
+#endif
+#if defined(X509_V_ERR_SUITE_B_INVALID_ALGORITHM)
+    {
+        X509_V_ERR_SUITE_B_INVALID_ALGORITHM, // 57
+        "X509_V_ERR_SUITE_B_INVALID_ALGORITHM"
+    },
+#endif
+#if defined(X509_V_ERR_SUITE_B_INVALID_CURVE)
+    {
+        X509_V_ERR_SUITE_B_INVALID_CURVE, // 58
+        "X509_V_ERR_SUITE_B_INVALID_CURVE"
+    },
+#endif
+#if defined(X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM)
+    {
+        X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM, // 59
+        "X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM"
+    },
+#endif
+#if defined(X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED)
+    {
+        X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED, // 60
+        "X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED"
+    },
+#endif
+#if defined(X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256)
+    {
+        X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256, // 61
+        "X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256"
+    },
+#endif
+#if defined(X509_V_ERR_HOSTNAME_MISMATCH)
+    {
+        X509_V_ERR_HOSTNAME_MISMATCH, // 62
+        "X509_V_ERR_HOSTNAME_MISMATCH"
+    },
+#endif
+#if defined(X509_V_ERR_EMAIL_MISMATCH)
+    {
+        X509_V_ERR_EMAIL_MISMATCH, // 63
+        "X509_V_ERR_EMAIL_MISMATCH"
+    },
+#endif
+#if defined(X509_V_ERR_IP_ADDRESS_MISMATCH)
+    {
+        X509_V_ERR_IP_ADDRESS_MISMATCH, // 64
+        "X509_V_ERR_IP_ADDRESS_MISMATCH"
+    },
+#endif
+#if defined(X509_V_ERR_DANE_NO_MATCH)
+    {
+        X509_V_ERR_DANE_NO_MATCH, // 65
+        "X509_V_ERR_DANE_NO_MATCH"
+    },
+#endif
+#if defined(X509_V_ERR_EE_KEY_TOO_SMALL)
+    {
+        X509_V_ERR_EE_KEY_TOO_SMALL, // 66
+        "X509_V_ERR_EE_KEY_TOO_SMALL"
+    },
+#endif
+#if defined(X509_V_ERR_CA_KEY_TOO_SMALL)
+    {
+        X509_V_ERR_CA_KEY_TOO_SMALL, // 67
+        "X509_V_ERR_CA_KEY_TOO_SMALL"
+    },
+#endif
+#if defined(X509_V_ERR_CA_MD_TOO_WEAK)
+    {
+        X509_V_ERR_CA_MD_TOO_WEAK, // 68
+        "X509_V_ERR_CA_MD_TOO_WEAK"
+    },
+#endif
+#if defined(X509_V_ERR_INVALID_CALL)
+    {
+        X509_V_ERR_INVALID_CALL, // 69
+        "X509_V_ERR_INVALID_CALL"
+    },
+#endif
+#if defined(X509_V_ERR_STORE_LOOKUP)
+    {
+        X509_V_ERR_STORE_LOOKUP, // 70
+        "X509_V_ERR_STORE_LOOKUP"
+    },
+#endif
+#if defined(X509_V_ERR_NO_VALID_SCTS)
+    {
+        X509_V_ERR_NO_VALID_SCTS, // 71
+        "X509_V_ERR_NO_VALID_SCTS"
+    },
+#endif
+#if defined(X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION)
+    {
+        X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION, // 72
+        "X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION"
+    },
+#endif
+#if defined(X509_V_ERR_OCSP_VERIFY_NEEDED)
+    {
+        X509_V_ERR_OCSP_VERIFY_NEEDED, // 73
+        "X509_V_ERR_OCSP_VERIFY_NEEDED"
+    },
+#endif
+#if defined(X509_V_ERR_OCSP_VERIFY_FAILED)
+    {
+        X509_V_ERR_OCSP_VERIFY_FAILED, // 74
+        "X509_V_ERR_OCSP_VERIFY_FAILED"
+    },
+#endif
+#if defined(X509_V_ERR_OCSP_CERT_UNKNOWN)
+    {
+        X509_V_ERR_OCSP_CERT_UNKNOWN, // 75
+        "X509_V_ERR_OCSP_CERT_UNKNOWN"
+    },
+#endif
+    {
+        SSL_ERROR_NONE,
+        "SSL_ERROR_NONE"
+    },
+#endif // USE_OPENSSL
+};
+
+} // namespace Security
+
+Security::ErrorCode
+Security::ErrorCodeFromName(const char *name)
+{
+    static auto TheCmp = [](const char *a, const char *b){return strcmp(a, b) < 0;};
+    static std::map<const char *, ErrorCode, decltype(TheCmp)> TheErrorCodeByNameIndx(TheCmp);
+    if (TheErrorCodeByNameIndx.empty()) {
+        for (const auto &i: TheErrorCodeNames)
+            TheErrorCodeByNameIndx.insert(std::make_pair(i.second, i.first));
+
+        // redirector to support legacy error translations
+        TheErrorCodeByNameIndx.insert(std::make_pair("SQUID_ERR_SSL_HANDSHAKE", SQUID_TLS_ERR_CONNECT));
+    }
+
+    const auto it = TheErrorCodeByNameIndx.find(name);
+    if (it != TheErrorCodeByNameIndx.end())
+        return it->second;
+
+    return 0;
+}
+
+const char *
+Security::ErrorNameFromCode(const ErrorCode err, const bool prefixRawCode)
+{
+    const auto it = TheErrorCodeNames.find(err);
+    if (it != TheErrorCodeNames.end())
+        return it->second;
+
+    static char tmpBuffer[128];
+    snprintf(tmpBuffer, sizeof(tmpBuffer), "%s%d",
+             (prefixRawCode ? "SSL_ERR=" : ""), static_cast<int>(err));
+    return tmpBuffer;
+}
+
+/* Security::ErrorDetail */
+
+uint64_t Security::ErrorDetail::Generations = 0;
+
+/// helper constructor implementing the logic shared by the two public ones
+Security::ErrorDetail::ErrorDetail(const ErrorCode err, const int aSysErrorNo):
+    generation(++Generations),
+    error_no(err),
+    // We could restrict errno(3) collection to cases where the TLS library
+    // explicitly talks about the errno being set, but correctly detecting those
+    // cases is difficult. We simplify in hope that all other cases will either
+    // have a useful errno or a zero errno.
+    sysErrorNo(aSysErrorNo)
+{
+#if USE_OPENSSL
+    /// Extract and remember errors stored internally by the TLS library.
+    if ((lib_error_no = ERR_get_error())) {
+        debugs(83, 7, "got " << asHex(lib_error_no));
+        // more errors may be stacked
+        // TODO: Save/detail all stacked errors by always flushing stale ones.
+        ForgetErrors();
+    }
+#else
+    // other libraries return errors explicitly instead of auto-storing them
+#endif
+}
+
+Security::ErrorDetail::ErrorDetail(const ErrorCode anErrorCode, const CertPointer &cert, const CertPointer &broken, const char *aReason):
+    ErrorDetail(anErrorCode, 0)
+{
+    errReason = aReason;
+    peer_cert = cert;
+    broken_cert = broken ? broken : cert;
+}
+
+#if USE_OPENSSL
+Security::ErrorDetail::ErrorDetail(const ErrorCode anErrorCode, const int anIoErrorNo, const int aSysErrorNo):
+    ErrorDetail(anErrorCode, aSysErrorNo)
+{
+    ioErrorNo = anIoErrorNo;
+}
+
+#elif USE_GNUTLS
+Security::ErrorDetail::ErrorDetail(const ErrorCode anErrorCode, const LibErrorCode aLibErrorNo, const int aSysErrorNo):
+    ErrorDetail(anErrorCode, aSysErrorNo)
+{
+    lib_error_no = aLibErrorNo;
+}
+#endif
+
+void
+Security::ErrorDetail::setPeerCertificate(const CertPointer &cert)
+{
+    assert(cert);
+    assert(!peer_cert);
+    assert(!broken_cert);
+    peer_cert = cert;
+    // unlike the constructor, the supplied certificate is not a broken_cert
+}
+
+SBuf
+Security::ErrorDetail::brief() const
+{
+    SBuf buf(err_code()); // TODO: Upgrade err_code()/etc. to return SBuf.
+
+    if (lib_error_no) {
+#if USE_OPENSSL
+        // TODO: Log ERR_error_string_n() instead, despite length, whitespace?
+        // Example: `error:1408F09C:SSL routines:ssl3_get_record:http request`.
+        buf.append(ToSBuf("+TLS_LIB_ERR=", std::hex, std::uppercase, lib_error_no));
+#elif USE_GNUTLS
+        buf.append(ToSBuf("+", gnutls_strerror_name(lib_error_no)));
+#endif
+    }
+
+#if USE_OPENSSL
+    // TODO: Consider logging long but human-friendly names (e.g.,
+    // SSL_ERROR_SYSCALL).
+    if (ioErrorNo)
+        buf.append(ToSBuf("+TLS_IO_ERR=", ioErrorNo));
+#endif
+
+    if (sysErrorNo) {
+        buf.append('+');
+        buf.append(SysErrorDetail::Brief(sysErrorNo));
+    }
+
+    if (broken_cert)
+        buf.append("+broken_cert");
+
+    return buf;
+}
+
+SBuf
+Security::ErrorDetail::verbose(const HttpRequestPointer &request) const
+{
+    char const *format = nullptr;
+#if USE_OPENSSL
+    if (Ssl::ErrorDetailsManager::GetInstance().getErrorDetail(error_no, request, detailEntry))
+        format = detailEntry.detail.termedBuf();
+#endif
+    if (!format)
+        format = "SSL handshake error (%err_name)";
+
+    SBuf errDetailStr;
+    assert(format);
+    auto remainder = format;
+    while (auto p = strchr(remainder, '%')) {
+        errDetailStr.append(remainder, p - remainder);
+        char const *converted = nullptr;
+        const auto formattingCodeLen = convert(++p, &converted);
+        if (formattingCodeLen)
+            errDetailStr.append(converted);
+        else
+            errDetailStr.append("%");
+        remainder = p + formattingCodeLen;
+    }
+    errDetailStr.append(remainder, strlen(remainder));
+    return errDetailStr;
+}
+
+/// textual representation of the subject of the broken certificate
+const char *
+Security::ErrorDetail::subject() const
+{
+#if USE_OPENSSL
+    if (broken_cert.get()) {
+        static char tmpBuffer[256]; // A temporary buffer
+        if (X509_NAME_oneline(X509_get_subject_name(broken_cert.get()), tmpBuffer, sizeof(tmpBuffer))) {
+            // quote to avoid possible html code injection through
+            // certificate subject
+            return html_quote(tmpBuffer);
+        }
+    }
+#endif // USE_OPENSSL
+    return "[Not available]";
+}
+
+#if USE_OPENSSL
+/// helper function to collect CNs using Ssl::matchX509CommonNames()
+static int
+copy_cn(void *check_data,  ASN1_STRING *cn_data)
+{
+    const auto str = static_cast<String*>(check_data);
+    if (!str) // no data? abort
+        return 0;
+    if (cn_data && cn_data->length) {
+        if (str->size() > 0)
+            str->append(", ");
+        str->append(reinterpret_cast<const char *>(cn_data->data), cn_data->length);
+    }
+    return 1;
+}
+#endif // USE_OPENSSL
+
+/// a list of the broken certificates CN and alternate names
+const char *
+Security::ErrorDetail::cn() const
+{
+#if USE_OPENSSL
+    if (broken_cert.get()) {
+        static String tmpStr;
+        tmpStr.clean();
+        Ssl::matchX509CommonNames(broken_cert.get(), &tmpStr, copy_cn);
+        if (tmpStr.size()) {
+            // quote to avoid possible HTML code injection through
+            // certificate subject
+            return html_quote(tmpStr.termedBuf());
+        }
+    }
+#endif // USE_OPENSSL
+    return "[Not available]";
+}
+
+/// the issuer of the broken certificate
+const char *
+Security::ErrorDetail::ca_name() const
+{
+#if USE_OPENSSL
+    if (broken_cert.get()) {
+        static char tmpBuffer[256]; // A temporary buffer
+        if (X509_NAME_oneline(X509_get_issuer_name(broken_cert.get()), tmpBuffer, sizeof(tmpBuffer))) {
+            // quote to avoid possible html code injection through
+            // certificate issuer subject
+            return html_quote(tmpBuffer);
+        }
+    }
+#endif // USE_OPENSSL
+    return "[Not available]";
+}
+
+/// textual representation of the "not before" field of the broken certificate
+const char *
+Security::ErrorDetail::notbefore() const
+{
+#if USE_OPENSSL
+    if (broken_cert.get()) {
+        if (const auto tm = X509_getm_notBefore(broken_cert.get())) {
+            static char tmpBuffer[256]; // A temporary buffer
+            Ssl::asn1timeToString(tm, tmpBuffer, sizeof(tmpBuffer));
+            return tmpBuffer;
+        }
+    }
+#endif // USE_OPENSSL
+    return "[Not available]";
+}
+
+/// textual representation of the "not after" field of the broken certificate
+const char *
+Security::ErrorDetail::notafter() const
+{
+#if USE_OPENSSL
+    if (broken_cert.get()) {
+        if (const auto tm = X509_getm_notAfter(broken_cert.get())) {
+            static char tmpBuffer[256]; // A temporary buffer
+            Ssl::asn1timeToString(tm, tmpBuffer, sizeof(tmpBuffer));
+            return tmpBuffer;
+        }
+    }
+#endif // USE_OPENSSL
+    return "[Not available]";
+}
+
+/// textual representation of error_no
+const char *
+Security::ErrorDetail::err_code() const
+{
+#if USE_OPENSSL
+    // try detailEntry first because it is faster
+    if (const char *err = detailEntry.name.termedBuf())
+        return err;
+#endif
+
+    return ErrorNameFromCode(error_no);
+}
+
+/// short description of error_no
+const char *
+Security::ErrorDetail::err_descr() const
+{
+    if (!error_no)
+        return "[No Error]";
+#if USE_OPENSSL
+    if (const char *err = detailEntry.descr.termedBuf())
+        return err;
+#endif
+    return "[Not available]";
+}
+
+/// textual representation of lib_error_no
+const char *
+Security::ErrorDetail::err_lib_error() const
+{
+    if (errReason.size() > 0)
+        return errReason.termedBuf();
+    else if (lib_error_no)
+        return ErrorString(lib_error_no);
+    else
+        return "[No Error]";
+    return "[Not available]";
+}
+
+/**
+ * Converts the code to a string value. Supported formatting codes are:
+ *
+ * Error meta information:
+ * %err_name: The name of a high-level SSL error (e.g., X509_V_ERR_*)
+ * %ssl_error_descr: A short description of the SSL error
+ * %ssl_lib_error: human-readable low-level error string by ErrorString()
+ *
+ * Certificate information extracted from broken (not necessarily peer!) cert
+ * %ssl_cn: The comma-separated list of common and alternate names
+ * %ssl_subject: The certificate subject
+ * %ssl_ca_name: The certificate issuer name
+ * %ssl_notbefore: The certificate "not before" field
+ * %ssl_notafter: The certificate "not after" field
+ *
+ \returns the length of the code (the number of characters to be replaced by value)
+ \retval 0 for unsupported codes
+*/
+size_t
+Security::ErrorDetail::convert(const char *code, const char **value) const
+{
+    typedef const char *(ErrorDetail::*PartDescriber)() const;
+    static const std::map<const char*, PartDescriber> PartDescriberByCode = {
+        {"ssl_subject", &ErrorDetail::subject},
+        {"ssl_ca_name", &ErrorDetail::ca_name},
+        {"ssl_cn", &ErrorDetail::cn},
+        {"ssl_notbefore", &ErrorDetail::notbefore},
+        {"ssl_notafter", &ErrorDetail::notafter},
+        {"err_name", &ErrorDetail::err_code},
+        {"ssl_error_descr", &ErrorDetail::err_descr},
+        {"ssl_lib_error", &ErrorDetail::err_lib_error}
+    };
+
+    for (const auto &pair: PartDescriberByCode) {
+        const auto len = strlen(pair.first);
+        if (strncmp(code, pair.first, len) == 0) {
+            const auto method = pair.second;
+            *value = (this->*method)();
+            return len;
+        }
+    }
+
+    // TODO: Support logformat %codes.
+    *value = ""; // unused with zero return
+    return 0;
+}
+
diff --git a/src/security/ErrorDetail.h b/src/security/ErrorDetail.h
new file mode 100644 (file)
index 0000000..89e3be9
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#ifndef SQUID_SRC_SECURITY_DETAIL_H
+#define SQUID_SRC_SECURITY_DETAIL_H
+
+#include "base/RefCount.h"
+#include "error/Detail.h"
+#include "http/forward.h"
+#include "security/forward.h"
+#include "SquidString.h"
+
+#if USE_OPENSSL
+#include "ssl/ErrorDetailManager.h"
+#endif
+
+namespace Security {
+
+/// Details a TLS-related error. Two kinds of errors can be detailed:
+/// * certificate validation errors (including built-in and helper-driven) and
+/// * TLS logic and I/O errors (detected by Squid or the TLS library).
+///
+/// The following details may be available (only the first one is required):
+/// * for all errors: problem classification (\see ErrorCode)
+/// * for all errors: peer certificate
+/// * for certificate validation errors: the broken certificate
+/// * for certificate validation errors: validation failure reason
+/// * for non-validation errors: TLS library-reported error(s)
+/// * for non-validation errors: system call errno(3)
+class ErrorDetail: public ::ErrorDetail
+{
+    MEMPROXY_CLASS(Security::ErrorDetail);
+
+public:
+    typedef ErrorDetailPointer Pointer;
+
+    /// Details a server-side certificate verification failure.
+    /// If `broken` is nil, then the broken certificate is the peer certificate.
+    ErrorDetail(ErrorCode err_no, const CertPointer &peer, const CertPointer &broken, const char *aReason = NULL);
+
+#if USE_OPENSSL
+    /// Details (or starts detailing) a non-validation failure.
+    /// \param anIoErrorNo TLS I/O function outcome; \see ErrorDetail::ioErrorNo
+    /// \param aSysErrorNo saved errno(3); \see ErrorDetail::sysErrorNo
+    ErrorDetail(ErrorCode anErrorCode, int anIoErrorNo, int aSysErrorNo);
+#elif USE_GNUTLS
+    /// Details (or starts detailing) a non-validation failure.
+    /// \param anLibErrorNo TLS function outcome; \see ErrorDetail::lib_error_no
+    /// \param aSysErrorNo saved errno(3); \see ErrorDetail::sysErrorNo
+    ErrorDetail(ErrorCode anErrorCode, LibErrorCode aLibErrorNo, int aSysErrorNo);
+#endif
+
+    /// \returns whether we (rather than `them`) should detail ErrorState
+    bool takesPriorityOver(const ErrorDetail &them) const {
+        // to reduce pointless updates, return false if us is them
+        return this->generation < them.generation;
+    }
+
+    /* ErrorDetail API */
+    virtual SBuf brief() const;
+    virtual SBuf verbose(const HttpRequestPointer &) const;
+
+    /// \returns error category; \see ErrorCode
+    ErrorCode errorNo() const { return error_no; }
+
+    /// \returns the previously saved errno(3) or zero
+    int sysError() const { return sysErrorNo; }
+
+    /* Certificate manipulation API. TODO: Add GnuTLS implementations, users. */
+
+    /// the peer certificate (or nil)
+    Certificate *peerCert() { return peer_cert.get(); }
+
+    /// peer or intermediate certificate that failed validation (or nil)
+    Certificate *brokenCert() {return broken_cert.get(); }
+
+    /// remember the SSL certificate of our peer; requires nil peerCert()
+    /// unlike the cert-setting constructor, does not assume the cert is bad
+    void setPeerCertificate(const CertPointer &);
+
+private:
+    ErrorDetail(ErrorCode err, int aSysErrorNo);
+
+    /* methods for formatting error details using admin-configurable %codes */
+    const char *subject() const;
+    const char *ca_name() const;
+    const char *cn() const;
+    const char *notbefore() const;
+    const char *notafter() const;
+    const char *err_code() const;
+    const char *err_descr() const;
+    const char *err_lib_error() const;
+    size_t convert(const char *code, const char **value) const;
+
+    static uint64_t Generations; ///< the total number of ErrorDetails ever made
+    uint64_t generation; ///< the number of ErrorDetails made before us plus one
+
+    CertPointer peer_cert; ///< A pointer to the peer certificate
+    CertPointer broken_cert; ///< A pointer to the broken certificate (peer or intermediate)
+
+    /// Squid-discovered error, validation error, or zero; \see ErrorCode
+    ErrorCode error_no = 0;
+
+    /// TLS library-reported non-validation error or zero; \see LibErrorCode
+    LibErrorCode lib_error_no = 0;
+
+    /// errno(3); system call failure code or zero
+    int sysErrorNo = 0;
+
+#if USE_OPENSSL
+    /// OpenSSL-specific (first-level or intermediate) TLS I/O operation result
+    /// reported by SSL_get_error(3SSL) (e.g., SSL_ERROR_SYSCALL) or zero.
+    /// Unlike lib_error_no, this error is mostly meant for I/O control and has
+    /// no OpenSSL-provided human-friendly text representation.
+    int ioErrorNo = 0;
+
+    using ErrorDetailEntry = Ssl::ErrorDetailEntry;
+    mutable ErrorDetailEntry detailEntry;
+#else
+    // other TLS libraries do not use custom ErrorDetail members
+#endif
+
+    String errReason; ///< a custom reason for the error
+};
+
+/// \returns ErrorCode with a given name (or zero)
+ErrorCode ErrorCodeFromName(const char *name);
+
+/// \returns string representation of ErrorCode, including raw X.509 error codes
+/// \param prefixRawCode whether to prefix raw codes with "SSL_ERR="
+const char *ErrorNameFromCode(ErrorCode err, bool prefixRawCode = false);
+
+}
+
+#endif
+
diff --git a/src/security/Io.cc b/src/security/Io.cc
new file mode 100644 (file)
index 0000000..cc648e4
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+/* DEBUG: section 83    TLS I/O */
+
+#include "squid.h"
+#include "fde.h"
+#include "security/Io.h"
+
+namespace Security {
+
+template <typename Fun>
+static IoResult Handshake(Comm::Connection &, ErrorCode, Fun);
+static void PrepForIo();
+
+typedef SessionPointer::element_type *ConnectionPointer;
+
+} // namespace Security
+
+// TODO: Replace high-level ERR_get_error() calls with a new std::ostream
+// ReportErrors manipulator inside debugs(), followed by a ForgetErrors() call.
+void
+Security::ForgetErrors()
+{
+#if USE_OPENSSL
+    unsigned int reported = 0; // efficiently marks ForgetErrors() call boundary
+    while (const auto errorToForget = ERR_get_error())
+        debugs(83, 7, '#' << (++reported) << ": " << asHex(errorToForget));
+#endif
+}
+
+/// the steps necessary to perform before the upcoming TLS I/O
+/// to correctly interpret/detail the outcome of that I/O
+static void
+Security::PrepForIo()
+{
+    // flush earlier errors that some call forgot to extract, so that we will
+    // only get the error(s) specific to the upcoming I/O operation
+    ForgetErrors();
+
+    // as the last step, reset errno to know when the I/O operation set it
+    errno = 0;
+}
+
+/// Calls the given TLS handshake function and analysis its outcome.
+/// Handles alert logging and being called without adequate TLS library support.
+template <typename Fun>
+static Security::IoResult
+Security::Handshake(Comm::Connection &transport, const ErrorCode topError, Fun ioCall)
+{
+    assert(transport.isOpen());
+    const auto fd = transport.fd;
+    auto connection = fd_table[fd].ssl.get();
+
+    PrepForIo();
+    const auto callResult = ioCall(connection);
+    const auto xerrno = errno;
+
+    debugs(83, 5, callResult << '/' << xerrno << " for TLS connection " <<
+           static_cast<void*>(connection) << " over " << transport);
+
+#if USE_OPENSSL
+    if (callResult > 0)
+        return IoResult(IoResult::ioSuccess);
+
+    const auto ioError = SSL_get_error(connection, callResult);
+
+    // quickly handle common, non-erroneous outcomes
+    switch (ioError) {
+
+    case SSL_ERROR_WANT_READ:
+        return IoResult(IoResult::ioWantRead);
+
+    case SSL_ERROR_WANT_WRITE:
+        return IoResult(IoResult::ioWantWrite);
+
+    default:
+        ; // fall through to handle the problem
+    }
+
+    // now we know that we are dealing with a real problem; detail it
+    const ErrorDetail::Pointer errorDetail =
+        new ErrorDetail(topError, ioError, xerrno);
+
+    IoResult ioResult(errorDetail);
+
+    // collect debugging-related details
+    switch (ioError) {
+    case SSL_ERROR_SYSCALL:
+        if (callResult == 0) {
+            ioResult.errorDescription = "peer aborted";
+        } else {
+            ioResult.errorDescription = "system call failure";
+            ioResult.important = (xerrno == ECONNRESET);
+        }
+        break;
+
+    case SSL_ERROR_ZERO_RETURN:
+        // peer sent a "close notify" alert, closing TLS connection for writing
+        ioResult.errorDescription = "peer closed";
+        ioResult.important = true;
+        break;
+
+    default:
+        // an ever-increasing number of possible cases but usually SSL_ERROR_SSL
+        ioResult.errorDescription = "failure";
+        ioResult.important = true;
+    }
+
+    return ioResult;
+
+#elif USE_GNUTLS
+    if (callResult == GNUTLS_E_SUCCESS) {
+        // TODO: Avoid gnutls_*() calls if debugging is off.
+        const auto desc = gnutls_session_get_desc(connection);
+        debugs(83, 2, "TLS session info: " << desc);
+        gnutls_free(desc);
+        return IoResult(IoResult::ioSuccess);
+    }
+
+    // Debug the TLS connection state so far.
+    // TODO: Avoid gnutls_*() calls if debugging is off.
+    const auto descIn = gnutls_handshake_get_last_in(connection);
+    debugs(83, 2, "handshake IN: " << gnutls_handshake_description_get_name(descIn));
+    const auto descOut = gnutls_handshake_get_last_out(connection);
+    debugs(83, 2, "handshake OUT: " << gnutls_handshake_description_get_name(descOut));
+
+    if (callResult == GNUTLS_E_WARNING_ALERT_RECEIVED) {
+        const auto alert = gnutls_alert_get(connection);
+        debugs(83, DBG_IMPORTANT, "WARNING: TLS alert: " << gnutls_alert_get_name(alert));
+        // fall through to retry
+    }
+
+    if (!gnutls_error_is_fatal(callResult)) {
+        const auto reading = gnutls_record_get_direction(connection) == 0;
+        return IoResult(reading ? IoResult::ioWantRead : IoResult::ioWantWrite);
+    }
+
+    // now we know that we are dealing with a real problem; detail it
+    const ErrorDetail::Pointer errorDetail =
+        new ErrorDetail(topError, callResult, xerrno);
+
+    IoResult ioResult(errorDetail);
+    ioResult.errorDescription = "failure";
+    return ioResult;
+
+#else
+    // TLS I/O code path should never be reachable without a TLS/SSL library.
+    debugs(1, DBG_CRITICAL, ForceAlert << "BUG: " <<
+           "Unexpected TLS I/O in Squid built without a TLS/SSL library");
+    assert(false); // we want a stack trace which fatal() does not produce
+    return IoResult(nullptr); // not reachable
+#endif
+}
+
+// TODO: After dropping OpenSSL v1.1.0 support, this and Security::Connect() can
+// be simplified further by using SSL_do_handshake() and eliminating lambdas.
+Security::IoResult
+Security::Accept(Comm::Connection &transport)
+{
+    return Handshake(transport, SQUID_TLS_ERR_ACCEPT, [] (ConnectionPointer tlsConn) {
+#if USE_OPENSSL
+        return SSL_accept(tlsConn);
+#elif USE_GNUTLS
+        return gnutls_handshake(tlsConn);
+#else
+        return sizeof(tlsConn); // the value is unused; should be unreachable
+#endif
+    });
+}
+
+/// establish a TLS connection over the specified from-Squid transport connection
+Security::IoResult
+Security::Connect(Comm::Connection &transport)
+{
+    return Handshake(transport, SQUID_TLS_ERR_CONNECT, [] (ConnectionPointer tlsConn) {
+#if USE_OPENSSL
+        return SSL_connect(tlsConn);
+#elif USE_GNUTLS
+        return gnutls_handshake(tlsConn);
+#else
+        return sizeof(tlsConn); // the value is unused; should be unreachable
+#endif
+    });
+}
+
diff --git a/src/security/Io.h b/src/security/Io.h
new file mode 100644 (file)
index 0000000..3a14b6f
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#ifndef SQUID_SRC_SECURITY_IO_H
+#define SQUID_SRC_SECURITY_IO_H
+
+#include "comm/forward.h"
+#include "security/forward.h"
+#include "security/ErrorDetail.h"
+
+namespace Security {
+
+/// a summary a TLS I/O operation outcome
+class IoResult {
+public:
+    /// all possible outcome cases
+    typedef enum { ioSuccess, ioWantRead, ioWantWrite, ioError } Category;
+
+    explicit IoResult(const Category aCategory): category(aCategory) {}
+    explicit IoResult(const ErrorDetailPointer &anErrorDetail): errorDetail(anErrorDetail) {}
+
+    /// convenience wrapper to detect successful I/O outcome; implies !wantsIo()
+    bool successful() const { return category == ioSuccess; }
+
+    /// convenience wrapper to detect whether more I/O is needed
+    bool wantsIo() const { return category == ioWantRead || category == ioWantWrite; }
+
+    ErrorDetailPointer errorDetail; ///< ioError case details (or nil)
+
+    Category category = ioError; ///< primary outcome classification
+
+    /* the data members below facilitate human-friendly debugging */
+    const char *errorDescription = nullptr; ///< a brief description of an error
+    bool important = false; ///< whether the error was serious/unusual
+};
+
+/// accept a TLS connection over the specified to-Squid transport connection
+IoResult Accept(Comm::Connection &transport);
+
+/// establish a TLS connection over the specified from-Squid transport connection
+IoResult Connect(Comm::Connection &transport);
+
+/// clear any errors that a TLS library has accumulated in its global storage
+void ForgetErrors();
+
+} // namespace Security
+
+#endif /* SQUID_SRC_SECURITY_IO_H */
+
index 95b82fd2d3e84fc4cf7eba204463b0d00cc80082..ac165adc1bd7babc57b3462385690aa501b7228b 100644 (file)
@@ -39,7 +39,7 @@ Security::KeyData::loadX509CertFromFile()
 #elif USE_GNUTLS
     const char *certFilename = certFile.c_str();
     gnutls_datum_t data;
-    Security::ErrorCode x = gnutls_load_file(certFilename, &data);
+    Security::LibErrorCode x = gnutls_load_file(certFilename, &data);
     if (x != GNUTLS_E_SUCCESS) {
         debugs(83, DBG_IMPORTANT, "ERROR: unable to load certificate file '" << certFile << "': " << ErrorString(x));
         return false;
index e75acf7b36079030839448eb773bd320d65f2a2c..998466b02983cdef73f463f86ac82c522d41bac5 100644 (file)
@@ -57,13 +57,19 @@ public:
     /// a helper label to simplify this objects API definitions below
     typedef Security::LockingPointer<T, UnLocker, Locker> SelfType;
 
+    /// constructs a nil smart pointer
+    constexpr LockingPointer(): raw(nullptr) {}
+
+    /// constructs a nil smart pointer from nullptr
+    constexpr LockingPointer(std::nullptr_t): raw(nullptr) {}
+
     /**
-     * Construct directly from a raw pointer.
-     * This action requires that the producer of that pointer has already
-     * created one reference lock for the object pointed to.
-     * Our destructor will do the matching unlock.
+     * Construct directly from a (possibly nil) raw pointer. If the supplied
+     * pointer is not nil, it is expected that its producer has already created
+     * one reference lock for the object pointed to, and our destructor will do
+     * the matching unlock.
      */
-    explicit LockingPointer(T *t = nullptr): raw(nullptr) {
+    explicit LockingPointer(T *t): raw(nullptr) {
         // de-optimized for clarity about non-locking
         resetWithoutLocking(t);
     }
index f729ba159a40b112213dea1ada906ebf3b8d3b57..19340f4a0bf57793abffc23007655ef5f6261523 100644 (file)
@@ -19,8 +19,12 @@ libsecurity_la_SOURCES = \
        Context.h \
        EncryptorAnswer.cc \
        EncryptorAnswer.h \
+       ErrorDetail.h \
+       ErrorDetail.cc \
        Handshake.cc \
        Handshake.h \
+       Io.cc \
+       Io.h \
        KeyData.cc \
        KeyData.h \
        LockingPointer.h \
index c9510edd4a2896d8adfecabfd0c6a5779e625805..83986912fb7d555fcb183394f92b3cbd067ac5f8 100644 (file)
@@ -20,6 +20,7 @@
 #include "HttpRequest.h"
 #include "neighbors.h"
 #include "pconn.h"
+#include "security/Io.h"
 #include "security/NegotiationHistory.h"
 #include "security/PeerConnector.h"
 #include "SquidConfig.h"
@@ -90,9 +91,8 @@ Security::PeerConnector::commCloseHandler(const CommCloseCbParams &params)
 
     debugs(83, 5, "FD " << params.fd << ", Security::PeerConnector=" << params.data);
     const auto err = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request.getRaw(), al);
-#if USE_OPENSSL
-    err->detail = new Ssl::ErrorDetail(SQUID_ERR_SSL_HANDSHAKE, nullptr, nullptr);
-#endif
+    static const auto d = MakeNamedErrorDetail("TLS_CONNECT_CLOSE");
+    err->detailError(d);
     bail(err);
 }
 
@@ -101,9 +101,8 @@ Security::PeerConnector::commTimeoutHandler(const CommTimeoutCbParams &)
 {
     debugs(83, 5, serverConnection() << " timedout. this=" << (void*)this);
     const auto err = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scGatewayTimeout, request.getRaw(), al);
-#if USE_OPENSSL
-    err->detail = new Ssl::ErrorDetail(SQUID_ERR_SSL_HANDSHAKE, nullptr, nullptr);
-#endif
+    static const auto d = MakeNamedErrorDetail("TLS_CONNECT_TIMEOUT");
+    err->detailError(d);
     bail(err);
 }
 
@@ -173,41 +172,31 @@ Security::PeerConnector::negotiate()
     if (fd_table[fd].closing())
         return;
 
-#if USE_OPENSSL
-    auto session = fd_table[fd].ssl.get();
-    debugs(83, 5, "SSL_connect session=" << (void*)session);
-    const int result = SSL_connect(session);
-    if (result <= 0) {
-#elif USE_GNUTLS
-    auto session = fd_table[fd].ssl.get();
-    const int result = gnutls_handshake(session);
-    debugs(83, 5, "gnutls_handshake session=" << (void*)session << ", result=" << result);
-
-    if (result == GNUTLS_E_SUCCESS) {
-        char *desc = gnutls_session_get_desc(session);
-        debugs(83, 2, serverConnection() << " TLS Session info: " << desc);
-        gnutls_free(desc);
-    }
-
-    if (result != GNUTLS_E_SUCCESS) {
-        // debug the TLS session state so far
-        auto descIn = gnutls_handshake_get_last_in(session);
-        debugs(83, 2, "handshake IN: " << gnutls_handshake_description_get_name(descIn));
-        auto descOut = gnutls_handshake_get_last_out(session);
-        debugs(83, 2, "handshake OUT: " << gnutls_handshake_description_get_name(descOut));
-#else
-    if (const int result = -1) {
-#endif
-        handleNegotiateError(result);
-        return; // we might be gone by now
-    }
+    const auto result = Security::Connect(*serverConnection());
+    switch (result.category) {
+    case Security::IoResult::ioSuccess:
+        recordNegotiationDetails();
+        if (sslFinalized())
+            sendSuccess();
+        return; // we may be gone by now
 
-    recordNegotiationDetails();
+    case Security::IoResult::ioWantRead:
+        noteWantRead();
+        return;
 
-    if (!sslFinalized())
+    case Security::IoResult::ioWantWrite:
+        noteWantWrite();
         return;
 
-    sendSuccess();
+    case Security::IoResult::ioError:
+        break; // fall through to error handling
+    }
+
+    // TODO: Honor result.important when working in a reverse proxy role?
+    debugs(83, 2, "ERROR: " << result.errorDescription <<
+           " while establishing TLS connection on FD: " << fd << result.errorDetail);
+    recordNegotiationDetails();
+    noteNegotiationError(result.errorDetail);
 }
 
 bool
@@ -259,7 +248,7 @@ Security::PeerConnector::sslCrtvdHandleReply(Ssl::CertValidationResponse::Pointe
 {
     Must(validationResponse != NULL);
 
-    Ssl::ErrorDetail *errDetails = NULL;
+    ErrorDetail::Pointer errDetails;
     bool validatorFailed = false;
 
     if (Debug::Enabled(83, 5)) {
@@ -289,7 +278,7 @@ Security::PeerConnector::sslCrtvdHandleReply(Ssl::CertValidationResponse::Pointe
         anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al);
     }  else {
         anErr =  new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request.getRaw(), al);
-        anErr->detail = errDetails;
+        anErr->detailError(errDetails);
         /*anErr->xerrno= Should preserved*/
     }
 
@@ -304,7 +293,7 @@ Security::PeerConnector::sslCrtvdHandleReply(Ssl::CertValidationResponse::Pointe
 /// The first honored error, if any, is returned via errDetails parameter.
 /// The method returns all seen errors except SSL_ERROR_NONE as Security::CertErrors.
 Security::CertErrors *
-Security::PeerConnector::sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &resp, Ssl::ErrorDetail *& errDetails)
+Security::PeerConnector::sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &resp, ErrorDetail::Pointer &errDetails)
 {
     ACLFilledChecklist *check = NULL;
     Security::SessionPointer session(fd_table[serverConnection()->fd].ssl);
@@ -337,10 +326,10 @@ Security::PeerConnector::sslCrtvdCheckForErrors(Ssl::CertValidationResponse cons
                 debugs(83, 3, "bypassing SSL error " << i->error_no << " in " << "buffer");
             } else {
                 debugs(83, 5, "confirming SSL error " << i->error_no);
-                X509 *brokenCert = i->cert.get();
+                const auto &brokenCert = i->cert;
                 Security::CertPointer peerCert(SSL_get_peer_certificate(session.get()));
                 const char *aReason = i->error_reason.empty() ? NULL : i->error_reason.c_str();
-                errDetails = new Ssl::ErrorDetail(i->error_no, peerCert.get(), brokenCert, aReason);
+                errDetails = new ErrorDetail(i->error_no, peerCert, brokenCert, aReason);
             }
             if (check) {
                 delete check->sslErrors;
@@ -378,70 +367,6 @@ Security::PeerConnector::negotiateSsl()
     CallJobHere(83, 7, this, Security::PeerConnector, negotiate);
 }
 
-void
-Security::PeerConnector::handleNegotiateError(const int ret)
-{
-    const int fd = serverConnection()->fd;
-    const Security::SessionPointer session(fd_table[fd].ssl);
-    unsigned long ssl_lib_error = ret;
-
-#if USE_OPENSSL
-    const int ssl_error = SSL_get_error(session.get(), ret);
-
-    switch (ssl_error) {
-    case SSL_ERROR_WANT_READ:
-        noteWantRead();
-        return;
-
-    case SSL_ERROR_WANT_WRITE:
-        noteWantWrite();
-        return;
-
-    case SSL_ERROR_SSL:
-    case SSL_ERROR_SYSCALL:
-        ssl_lib_error = ERR_get_error();
-        // proceed to the general error handling code
-        break;
-    default:
-        // no special error handling for all other errors
-        ssl_lib_error = SSL_ERROR_NONE;
-        break;
-    }
-
-#elif USE_GNUTLS
-    const int ssl_error = ret;
-
-    switch (ret) {
-    case GNUTLS_E_WARNING_ALERT_RECEIVED: {
-        auto alert = gnutls_alert_get(session.get());
-        debugs(83, DBG_IMPORTANT, "TLS ALERT: " << gnutls_alert_get_name(alert));
-    }
-    // drop through to next case
-
-    case GNUTLS_E_AGAIN:
-    case GNUTLS_E_INTERRUPTED:
-        if (gnutls_record_get_direction(session.get()) == 0)
-            noteWantRead();
-        else
-            noteWantWrite();
-        return;
-
-    default:
-        // no special error handling for all other errors
-        break;
-    }
-
-#else
-    // this avoids unused variable compiler warnings.
-    Must(!session);
-    const int ssl_error = ret;
-#endif
-
-    // Log connection details, if any
-    recordNegotiationDetails();
-    noteNegotiationError(ret, ssl_error, ssl_lib_error);
-}
-
 void
 Security::PeerConnector::noteWantRead()
 {
@@ -490,48 +415,28 @@ Security::PeerConnector::noteWantWrite()
 }
 
 void
-Security::PeerConnector::noteNegotiationError(const int ret, const int ssl_error, const int ssl_lib_error)
+Security::PeerConnector::noteNegotiationError(const Security::ErrorDetailPointer &callerDetail)
 {
-#if defined(EPROTO)
-    int sysErrNo = EPROTO;
-#else
-    int sysErrNo = EACCES;
-#endif
-
+    auto primaryDetail = callerDetail;
 #if USE_OPENSSL
-    // store/report errno when ssl_error is SSL_ERROR_SYSCALL, ssl_lib_error is 0, and ret is -1
-    if (ssl_error == SSL_ERROR_SYSCALL && ret == -1 && ssl_lib_error == 0)
-        sysErrNo = errno;
-#endif
-    int xerr = errno;
-
-    const int fd = serverConnection()->fd;
-    debugs(83, DBG_IMPORTANT, "ERROR: negotiating TLS on FD " << fd <<
-           ": " << Security::ErrorString(ssl_lib_error) << " (" <<
-           ssl_error << "/" << ret << "/" << xerr << ")");
+    const auto tlsConnection = fd_table[serverConnection()->fd].ssl.get();
 
-    const auto anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request, al);
-    anErr->xerrno = sysErrNo;
-
-#if USE_OPENSSL
-    Security::SessionPointer session(fd_table[fd].ssl);
-    Ssl::ErrorDetail *errFromFailure = static_cast<Ssl::ErrorDetail *>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_error_detail));
-    if (errFromFailure != NULL) {
-        // The errFromFailure is attached to the ssl object
-        // and will be released when ssl object destroyed.
-        // Copy errFromFailure to a new Ssl::ErrorDetail object
-        anErr->detail = new Ssl::ErrorDetail(*errFromFailure);
-    } else {
-        // server_cert can be NULL here
-        X509 *server_cert = SSL_get_peer_certificate(session.get());
-        anErr->detail = new Ssl::ErrorDetail(SQUID_ERR_SSL_HANDSHAKE, server_cert, NULL);
-        X509_free(server_cert);
+    // find the highest priority detail
+    if (const auto storedDetailRaw = SSL_get_ex_data(tlsConnection, ssl_ex_index_ssl_error_detail)) {
+        const auto &storedDetail = *static_cast<ErrorDetail::Pointer*>(storedDetailRaw);
+        if (storedDetail->takesPriorityOver(*primaryDetail))
+            primaryDetail = storedDetail;
     }
 
-    if (ssl_lib_error != SSL_ERROR_NONE)
-        anErr->detail->setLibError(ssl_lib_error);
+    if (!primaryDetail->peerCert()) {
+        if (const auto serverCert = SSL_get_peer_certificate(tlsConnection))
+            primaryDetail->setPeerCertificate(CertPointer(serverCert));
+    }
 #endif
 
+    const auto anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request, al);
+    anErr->xerrno = primaryDetail->sysError();
+    anErr->detailError(primaryDetail);
     noteNegotiationDone(anErr);
     bail(anErr);
 }
index 2e5efceb19fd3f1b0dec9fcaad34234b0d863800..f0c7be97d874cb394da4c240eeb28f14551bde37 100644 (file)
@@ -119,10 +119,7 @@ protected:
     virtual void noteWantWrite();
 
     /// Called when the SSL_connect function aborts with an SSL negotiation error
-    /// \param result the SSL_connect return code
-    /// \param ssl_error the error code returned from the SSL_get_error function
-    /// \param ssl_lib_error the error returned from the ERR_Get_Error function
-    virtual void noteNegotiationError(const int result, const int ssl_error, const int ssl_lib_error);
+    virtual void noteNegotiationError(const Security::ErrorDetailPointer &);
 
     /// Called when the SSL negotiation to the server completed and the certificates
     /// validated using the cert validator.
@@ -168,7 +165,7 @@ private:
     void sslCrtvdHandleReply(Ssl::CertValidationResponsePointer);
 
     /// Check SSL errors returned from cert validator against sslproxy_cert_error access list
-    Security::CertErrors *sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &, Ssl::ErrorDetail *&);
+    Security::CertErrors *sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &, ErrorDetailPointer &);
 #endif
 
     static void NegotiateSsl(int fd, void *data);
index b8da60d95dfb44aeee6f92abc6b65c400e50dce0..a2e683b5239224b0a62e83bf2bda901704b71693 100644 (file)
@@ -254,7 +254,7 @@ Security::PeerOptions::createBlankContext() const
 #elif USE_GNUTLS
     // Initialize for X.509 certificate exchange
     gnutls_certificate_credentials_t t;
-    if (const int x = gnutls_certificate_allocate_credentials(&t)) {
+    if (const auto x = gnutls_certificate_allocate_credentials(&t)) {
         fatalf("Failed to allocate TLS client context: %s\n", Security::ErrorString(x));
     }
     ctx = convertContextFromRawPtr(t);
@@ -527,7 +527,7 @@ Security::PeerOptions::parseOptions()
     const char *err = nullptr;
     const char *priorities = str.c_str();
     gnutls_priority_t op;
-    int x = gnutls_priority_init(&op, priorities, &err);
+    const auto x = gnutls_priority_init(&op, priorities, &err);
     if (x != GNUTLS_E_SUCCESS) {
         fatalf("(%s) in TLS options '%s'", ErrorString(x), err);
     }
@@ -767,7 +767,7 @@ Security::PeerOptions::updateSessionOptions(Security::SessionPointer &s)
     SSL_set_options(s.get(), parsedOptions);
 
 #elif USE_GNUTLS
-    int x;
+    LibErrorCode x;
     SBuf errMsg;
     if (!parsedOptions) {
         debugs(83, 5, "set GnuTLS default priority/options for session=" << s);
index 182745638da2a47167fe5b79cb216b0e02c0cbca..f9da3ea4e9c2bace8aa04f84ec9c947d4d71d077 100644 (file)
@@ -169,7 +169,7 @@ Security::ServerOptions::createBlankContext() const
 #elif USE_GNUTLS
     // Initialize for X.509 certificate exchange
     gnutls_certificate_credentials_t t;
-    if (const int x = gnutls_certificate_allocate_credentials(&t)) {
+    if (const auto x = gnutls_certificate_allocate_credentials(&t)) {
         debugs(83, DBG_CRITICAL, "ERROR: Failed to allocate TLS server context: " << Security::ErrorString(x));
     }
     ctx = convertContextFromRawPtr(t);
index 3499e980b74c4548522a0b6e1fcbb53cb65f7240..183acbae8a97d1f1165220047f8daa1fc25c96fa 100644 (file)
@@ -116,7 +116,7 @@ CreateSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer
 #if USE_OPENSSL || USE_GNUTLS
 
     const char *errAction = "with no TLS/SSL library";
-    int errCode = 0;
+    Security::LibErrorCode errCode = 0;
 #if USE_OPENSSL
     Security::SessionPointer session(Security::NewSessionObject(ctx));
     if (!session) {
index 177328031d04d47b08d53588708b157518470356..fad247d5f0fac493919eb1bb354911170c8ada32 100644 (file)
@@ -10,6 +10,7 @@
 #define SQUID_SRC_SECURITY_FORWARD_H
 
 #include "base/CbDataList.h"
+#include "base/forward.h"
 #include "security/Context.h"
 #include "security/Session.h"
 
@@ -17,6 +18,7 @@
 #include <gnutls/abstract.h>
 #endif
 #include <list>
+#include <limits>
 #if USE_OPENSSL
 #include "compat/openssl.h"
 #if HAVE_OPENSSL_BN_H
@@ -59,13 +61,21 @@ class CertError;
 /// Holds a list of X.509 certificate errors
 typedef CbDataList<Security::CertError> CertErrors;
 
+#if USE_OPENSSL
+typedef X509 Certificate;
+#elif USE_GNUTLS
+typedef struct gnutls_x509_crt_int Certificate;
+#else
+typedef class {} Certificate;
+#endif
+
 #if USE_OPENSSL
 CtoCpp1(X509_free, X509 *);
 typedef Security::LockingPointer<X509, X509_free_cpp, HardFun<int, X509 *, X509_up_ref> > CertPointer;
 #elif USE_GNUTLS
 typedef std::shared_ptr<struct gnutls_x509_crt_int> CertPointer;
 #else
-typedef std::shared_ptr<void> CertPointer;
+typedef std::shared_ptr<Certificate> CertPointer;
 #endif
 
 #if USE_OPENSSL
@@ -91,10 +101,26 @@ typedef void *DhePointer;
 
 class EncryptorAnswer;
 
-/// Squid defined error code (<0), an error code returned by X.509 API, or SSL_ERROR_NONE
+/// Squid-defined error code (<0), an error code returned by X.509 API, or zero
 typedef int ErrorCode;
 
-inline const char *ErrorString(const ErrorCode code) {
+/// TLS library-reported non-validation error
+#if USE_OPENSSL
+/// the result of the first ERR_get_error(3SSL) call after a library call;
+/// `openssl errstr` expands these numbers into human-friendlier strings like
+/// `error:1408F09C:SSL routines:ssl3_get_record:http request`
+typedef unsigned long LibErrorCode;
+#elif USE_GNUTLS
+/// the result of an API function like gnutls_handshake() (e.g.,
+/// GNUTLS_E_WARNING_ALERT_RECEIVED)
+typedef int LibErrorCode;
+#else
+/// should always be zero and virtually unused
+typedef int LibErrorCode;
+#endif
+
+/// converts numeric LibErrorCode into a human-friendlier string
+inline const char *ErrorString(const LibErrorCode code) {
 #if USE_OPENSSL
     return ERR_error_string(code, nullptr);
 #elif USE_GNUTLS
@@ -127,6 +153,9 @@ enum Type {
 
 } // namespace Io
 
+// TODO: Either move to Security::Io or remove/restrict the Io namespace.
+class IoResult;
+
 class KeyData;
 
 #if USE_OPENSSL
@@ -156,7 +185,29 @@ typedef std::shared_ptr<void> PrivateKeyPointer;
 
 class ServerOptions;
 
+class ErrorDetail;
+typedef RefCount<ErrorDetail> ErrorDetailPointer;
+
 } // namespace Security
 
+/// Squid-specific TLS handling errors (a subset of ErrorCode)
+/// These errors either distinguish high-level library calls/contexts or
+/// supplement official certificate validation errors to cover special cases.
+/// We use negative values, assuming that those official errors are positive.
+enum {
+    SQUID_TLS_ERR_OFFSET = std::numeric_limits<int>::min(),
+
+    /* TLS library calls/contexts other than validation (e.g., I/O) */
+    SQUID_TLS_ERR_ACCEPT, ///< failure to accept a connection from a TLS client
+    SQUID_TLS_ERR_CONNECT, ///< failure to establish a connection with a TLS server
+
+    /* certificate validation problems not covered by official errors */
+    SQUID_X509_V_ERR_CERT_CHANGE,
+    SQUID_X509_V_ERR_DOMAIN_MISMATCH,
+    SQUID_X509_V_ERR_INFINITE_VALIDATION,
+
+    SQUID_TLS_ERR_END
+};
+
 #endif /* SQUID_SRC_SECURITY_FORWARD_H */
 
index 4da374344616d53091092f2488bccfa7bffc797a..549f1aac8882d0ea26f2a7035260fa055c129119 100644 (file)
@@ -807,7 +807,7 @@ Ftp::Server::handleReply(HttpReply *reply, StoreIOBuffer data)
 void
 Ftp::Server::handleFeatReply(const HttpReply *reply, StoreIOBuffer)
 {
-    if (pipeline.front()->http->request->errType != ERR_NONE) {
+    if (pipeline.front()->http->request->error) {
         writeCustomReply(502, "Server does not support FEAT", reply);
         return;
     }
@@ -879,7 +879,7 @@ Ftp::Server::handlePasvReply(const HttpReply *reply, StoreIOBuffer)
     const Http::StreamPointer context(pipeline.front());
     assert(context != nullptr);
 
-    if (context->http->request->errType != ERR_NONE) {
+    if (context->http->request->error) {
         writeCustomReply(502, "Server does not support PASV", reply);
         return;
     }
@@ -916,7 +916,7 @@ Ftp::Server::handlePasvReply(const HttpReply *reply, StoreIOBuffer)
 void
 Ftp::Server::handlePortReply(const HttpReply *reply, StoreIOBuffer)
 {
-    if (pipeline.front()->http->request->errType != ERR_NONE) {
+    if (pipeline.front()->http->request->error) {
         writeCustomReply(502, "Server does not support PASV (converted from PORT)", reply);
         return;
     }
@@ -1054,7 +1054,7 @@ Ftp::Server::writeForwardedReply(const HttpReply *reply)
 void
 Ftp::Server::handleEprtReply(const HttpReply *reply, StoreIOBuffer)
 {
-    if (pipeline.front()->http->request->errType != ERR_NONE) {
+    if (pipeline.front()->http->request->error) {
         writeCustomReply(502, "Server does not support PASV (converted from EPRT)", reply);
         return;
     }
@@ -1067,7 +1067,7 @@ Ftp::Server::handleEprtReply(const HttpReply *reply, StoreIOBuffer)
 void
 Ftp::Server::handleEpsvReply(const HttpReply *reply, StoreIOBuffer)
 {
-    if (pipeline.front()->http->request->errType != ERR_NONE) {
+    if (pipeline.front()->http->request->error) {
         writeCustomReply(502, "Cannot connect to server", reply);
         return;
     }
@@ -1096,14 +1096,12 @@ Ftp::Server::writeErrorReply(const HttpReply *reply, const int scode)
     MemBuf mb;
     mb.init();
 
-    if (request->errType != ERR_NONE)
-        mb.appendf("%i-%s\r\n", scode, errorPageName(request->errType));
+    if (request->error)
+        mb.appendf("%i-%s\r\n", scode, errorPageName(request->error.category));
 
-    if (request->errDetail > 0) {
-        // XXX: > 0 may not always mean that this is an errno
-        mb.appendf("%i-Error: (%d) %s\r\n", scode,
-                   request->errDetail,
-                   strerror(request->errDetail));
+    if (const auto &detail = request->error.detail) {
+        mb.appendf("%i-Error-Detail-Brief: " SQUIDSBUFPH "\r\n", scode, SQUIDSBUFPRINT(detail->brief()));
+        mb.appendf("%i-Error-Detail-Verbose: " SQUIDSBUFPH "\r\n", scode, SQUIDSBUFPRINT(detail->verbose(request)));
     }
 
 #if USE_ADAPTATION
index 865f347dc1d364bb9119dbf56653411783c8dc99..8eb03ea3fecfeb859f869f3aee32c1f283854067 100644 (file)
 #include "comm.h"
 #include "comm/Read.h"
 #include "Debug.h"
+#include "error/SysErrorDetail.h"
 #include "fd.h"
 #include "fde.h"
 #include "http/Stream.h"
+#include "LogTags.h"
 #include "MasterXaction.h"
 #include "servers/Server.h"
 #include "SquidConfig.h"
@@ -146,8 +148,10 @@ Server::doClientRead(const CommIoCbParams &io)
     case Comm::ENDFILE: // close detected by 0-byte read
         debugs(33, 5, io.conn << " closed?");
 
-        if (connFinishedWithConn(rd.size)) {
-            clientConnection->close();
+        if (shouldCloseOnEof()) {
+            LogTagsErrors lte;
+            lte.aborted = true;
+            terminateAll(ERR_CLIENT_GONE, lte);
             return;
         }
 
@@ -167,9 +171,10 @@ Server::doClientRead(const CommIoCbParams &io)
     // case Comm::COMM_ERROR:
     default: // no other flags should ever occur
         debugs(33, 2, io.conn << ": got flag " << rd.flag << "; " << xstrerr(rd.xerrno));
-        checkLogging();
-        pipeline.terminateAll(rd.xerrno);
-        io.conn->close();
+        LogTagsErrors lte;
+        lte.timedout = rd.xerrno == ETIMEDOUT;
+        lte.aborted = !lte.timedout; // intentionally true for zero rd.xerrno
+        terminateAll(Error(ERR_CLIENT_GONE, SysErrorDetail::NewIfAny(rd.xerrno)), lte);
         return;
     }
 
index 3a233da6a134151458d6ae8cce8442633d8d2414..6c139cc91a8dc1fc7defd35fcb523e30fc6c103d 100644 (file)
@@ -17,6 +17,7 @@
 #include "BodyPipe.h"
 #include "comm/Write.h"
 #include "CommCalls.h"
+#include "log/forward.h"
 #include "Pipeline.h"
 #include "sbuf/SBuf.h"
 #include "servers/forward.h"
@@ -36,8 +37,8 @@ public:
     virtual bool doneAll() const;
     virtual void swanSong();
 
-    /// ??
-    virtual bool connFinishedWithConn(int size) = 0;
+    /// whether to stop serving our client after reading EOF on its connection
+    virtual bool shouldCloseOnEof() const = 0;
 
     /// maybe grow the inBuf and schedule Comm::Read()
     void readSomeData();
@@ -115,12 +116,12 @@ public:
     Pipeline pipeline;
 
 protected:
+    /// abort any pending transactions and prevent new ones (by closing)
+    virtual void terminateAll(const Error &, const LogTagsErrors &) = 0;
+
     void doClientRead(const CommIoCbParams &io);
     void clientWriteDone(const CommIoCbParams &io);
 
-    /// Log the current [attempt at] transaction if nobody else will.
-    virtual void checkLogging() = 0;
-
     AsyncCall::Pointer reader; ///< set when we are reading
     AsyncCall::Pointer writer; ///< set when we are writing
 };
index c85c46a083e2cbcc0ff0d7c291938e7f58264354..1161cc162e6d84e57d54b94db7ff7de7419026b9 100644 (file)
 #include "squid.h"
 #include "errorpage.h"
 #include "fatal.h"
-#include "html_quote.h"
 #include "ssl/ErrorDetail.h"
+#include "ssl/ErrorDetailManager.h"
 
-#include <climits>
 #include <map>
 
-struct SslErrorEntry {
-    Security::ErrorCode value;
-    const char *name;
-};
-
-static const char *SslErrorDetailDefaultStr = "SSL handshake error (%err_name)";
-//Use std::map to optimize search
-typedef std::map<Security::ErrorCode, const SslErrorEntry *> SslErrors;
-SslErrors TheSslErrors;
-
-static SslErrorEntry TheSslErrorArray[] = {
-    {   SQUID_X509_V_ERR_INFINITE_VALIDATION,
-        "SQUID_X509_V_ERR_INFINITE_VALIDATION"
-    },
-    {   SQUID_X509_V_ERR_CERT_CHANGE,
-        "SQUID_X509_V_ERR_CERT_CHANGE"
-    },
-    {   SQUID_ERR_SSL_HANDSHAKE,
-        "SQUID_ERR_SSL_HANDSHAKE"
-    },
-    {   SQUID_X509_V_ERR_DOMAIN_MISMATCH,
-        "SQUID_X509_V_ERR_DOMAIN_MISMATCH"
-    },
-    {   X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT,
-        "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT"
-    },
-    {   X509_V_ERR_UNABLE_TO_GET_CRL,
-        "X509_V_ERR_UNABLE_TO_GET_CRL"
-    },
-    {   X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE,
-        "X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE"
-    },
-    {   X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE,
-        "X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE"
-    },
-    {   X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY,
-        "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY"
-    },
-    {   X509_V_ERR_CERT_SIGNATURE_FAILURE,
-        "X509_V_ERR_CERT_SIGNATURE_FAILURE"
-    },
-    {   X509_V_ERR_CRL_SIGNATURE_FAILURE,
-        "X509_V_ERR_CRL_SIGNATURE_FAILURE"
-    },
-    {   X509_V_ERR_CERT_NOT_YET_VALID,
-        "X509_V_ERR_CERT_NOT_YET_VALID"
-    },
-    {   X509_V_ERR_CERT_HAS_EXPIRED,
-        "X509_V_ERR_CERT_HAS_EXPIRED"
-    },
-    {   X509_V_ERR_CRL_NOT_YET_VALID,
-        "X509_V_ERR_CRL_NOT_YET_VALID"
-    },
-    {   X509_V_ERR_CRL_HAS_EXPIRED,
-        "X509_V_ERR_CRL_HAS_EXPIRED"
-    },
-    {   X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD,
-        "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD"
-    },
-    {   X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD,
-        "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD"
-    },
-    {   X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD,
-        "X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD"
-    },
-    {   X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD,
-        "X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD"
-    },
-    {   X509_V_ERR_OUT_OF_MEM,
-        "X509_V_ERR_OUT_OF_MEM"
-    },
-    {   X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,
-        "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
-    },
-    {   X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN,
-        "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN"
-    },
-    {   X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
-        "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
-    },
-    {   X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE,
-        "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
-    },
-    {   X509_V_ERR_CERT_CHAIN_TOO_LONG,
-        "X509_V_ERR_CERT_CHAIN_TOO_LONG"
-    },
-    {   X509_V_ERR_CERT_REVOKED,
-        "X509_V_ERR_CERT_REVOKED"
-    },
-    {   X509_V_ERR_INVALID_CA,
-        "X509_V_ERR_INVALID_CA"
-    },
-    {   X509_V_ERR_PATH_LENGTH_EXCEEDED,
-        "X509_V_ERR_PATH_LENGTH_EXCEEDED"
-    },
-    {   X509_V_ERR_INVALID_PURPOSE,
-        "X509_V_ERR_INVALID_PURPOSE"
-    },
-    {   X509_V_ERR_CERT_UNTRUSTED,
-        "X509_V_ERR_CERT_UNTRUSTED"
-    },
-    {   X509_V_ERR_CERT_REJECTED,
-        "X509_V_ERR_CERT_REJECTED"
-    },
-    {   X509_V_ERR_SUBJECT_ISSUER_MISMATCH,
-        "X509_V_ERR_SUBJECT_ISSUER_MISMATCH"
-    },
-    {   X509_V_ERR_AKID_SKID_MISMATCH,
-        "X509_V_ERR_AKID_SKID_MISMATCH"
-    },
-    {   X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH,
-        "X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH"
-    },
-    {   X509_V_ERR_KEYUSAGE_NO_CERTSIGN,
-        "X509_V_ERR_KEYUSAGE_NO_CERTSIGN"
-    },
-#if defined(X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER)
-    {
-        X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER, //33
-        "X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER"
-    },
-#endif
-#if defined(X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION)
-    {
-        X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION, //34
-        "X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION"
-    },
-#endif
-#if defined(X509_V_ERR_KEYUSAGE_NO_CRL_SIGN)
-    {
-        X509_V_ERR_KEYUSAGE_NO_CRL_SIGN, //35
-        "X509_V_ERR_KEYUSAGE_NO_CRL_SIGN"
-    },
-#endif
-#if defined(X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION)
-    {
-        X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION, //36
-        "X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION"
-    },
-#endif
-#if defined(X509_V_ERR_INVALID_NON_CA)
-    {
-        X509_V_ERR_INVALID_NON_CA, //37
-        "X509_V_ERR_INVALID_NON_CA"
-    },
-#endif
-#if defined(X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED)
-    {
-        X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED, //38
-        "X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED"
-    },
-#endif
-#if defined(X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE)
-    {
-        X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE, //39
-        "X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE"
-    },
-#endif
-#if defined(X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED)
-    {
-        X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED, //40
-        "X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED"
-    },
-#endif
-#if defined(X509_V_ERR_INVALID_EXTENSION)
-    {
-        X509_V_ERR_INVALID_EXTENSION, //41
-        "X509_V_ERR_INVALID_EXTENSION"
-    },
-#endif
-#if defined(X509_V_ERR_INVALID_POLICY_EXTENSION)
-    {
-        X509_V_ERR_INVALID_POLICY_EXTENSION, //42
-        "X509_V_ERR_INVALID_POLICY_EXTENSION"
-    },
-#endif
-#if defined(X509_V_ERR_NO_EXPLICIT_POLICY)
-    {
-        X509_V_ERR_NO_EXPLICIT_POLICY, //43
-        "X509_V_ERR_NO_EXPLICIT_POLICY"
-    },
-#endif
-#if defined(X509_V_ERR_DIFFERENT_CRL_SCOPE)
-    {
-        X509_V_ERR_DIFFERENT_CRL_SCOPE, //44
-        "X509_V_ERR_DIFFERENT_CRL_SCOPE"
-    },
-#endif
-#if defined(X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE)
-    {
-        X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE, //45
-        "X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE"
-    },
-#endif
-#if defined(X509_V_ERR_UNNESTED_RESOURCE)
-    {
-        X509_V_ERR_UNNESTED_RESOURCE, //46
-        "X509_V_ERR_UNNESTED_RESOURCE"
-    },
-#endif
-#if defined(X509_V_ERR_PERMITTED_VIOLATION)
-    {
-        X509_V_ERR_PERMITTED_VIOLATION, //47
-        "X509_V_ERR_PERMITTED_VIOLATION"
-    },
-#endif
-#if defined(X509_V_ERR_EXCLUDED_VIOLATION)
-    {
-        X509_V_ERR_EXCLUDED_VIOLATION, //48
-        "X509_V_ERR_EXCLUDED_VIOLATION"
-    },
-#endif
-#if defined(X509_V_ERR_SUBTREE_MINMAX)
-    {
-        X509_V_ERR_SUBTREE_MINMAX, //49
-        "X509_V_ERR_SUBTREE_MINMAX"
-    },
-#endif
-    {   X509_V_ERR_APPLICATION_VERIFICATION, //50
-        "X509_V_ERR_APPLICATION_VERIFICATION"
-    },
-#if defined(X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE)
-    {
-        X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE, //51
-        "X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE"
-    },
-#endif
-#if defined(X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX)
-    {
-        X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX, //52
-        "X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX"
-    },
-#endif
-#if defined(X509_V_ERR_UNSUPPORTED_NAME_SYNTAX)
-    {
-        X509_V_ERR_UNSUPPORTED_NAME_SYNTAX, //53
-        "X509_V_ERR_UNSUPPORTED_NAME_SYNTAX"
-    },
-#endif
-#if defined(X509_V_ERR_CRL_PATH_VALIDATION_ERROR)
-    {
-        X509_V_ERR_CRL_PATH_VALIDATION_ERROR, //54
-        "X509_V_ERR_CRL_PATH_VALIDATION_ERROR"
-    },
-#endif
-#if defined(X509_V_ERR_PATH_LOOP)
-    {
-        X509_V_ERR_PATH_LOOP, //55
-        "X509_V_ERR_PATH_LOOP"
-    },
-#endif
-#if defined(X509_V_ERR_SUITE_B_INVALID_VERSION)
-    {
-        X509_V_ERR_SUITE_B_INVALID_VERSION, //56
-        "X509_V_ERR_SUITE_B_INVALID_VERSION"
-    },
-#endif
-#if defined(X509_V_ERR_SUITE_B_INVALID_ALGORITHM)
-    {
-        X509_V_ERR_SUITE_B_INVALID_ALGORITHM, //57
-        "X509_V_ERR_SUITE_B_INVALID_ALGORITHM"
-    },
-#endif
-#if defined(X509_V_ERR_SUITE_B_INVALID_CURVE)
-    {
-        X509_V_ERR_SUITE_B_INVALID_CURVE, //58
-        "X509_V_ERR_SUITE_B_INVALID_CURVE"
-    },
-#endif
-#if defined(X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM)
-    {
-        X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM, //59
-        "X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM"
-    },
-#endif
-#if defined(X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED)
-    {
-        X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED, //60
-        "X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED"
-    },
-#endif
-#if defined(X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256)
-    {
-        X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256, //61
-        "X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256"
-    },
-#endif
-#if defined(X509_V_ERR_HOSTNAME_MISMATCH)
-    {
-        X509_V_ERR_HOSTNAME_MISMATCH, //62
-        "X509_V_ERR_HOSTNAME_MISMATCH"
-    },
-#endif
-#if defined(X509_V_ERR_EMAIL_MISMATCH)
-    {
-        X509_V_ERR_EMAIL_MISMATCH, //63
-        "X509_V_ERR_EMAIL_MISMATCH"
-    },
-#endif
-#if defined(X509_V_ERR_IP_ADDRESS_MISMATCH)
-    {
-        X509_V_ERR_IP_ADDRESS_MISMATCH, //64
-        "X509_V_ERR_IP_ADDRESS_MISMATCH"
-    },
-#endif
-#if defined(X509_V_ERR_DANE_NO_MATCH)
-    {
-        X509_V_ERR_DANE_NO_MATCH, //65
-        "X509_V_ERR_DANE_NO_MATCH"
-    },
-#endif
-#if defined(X509_V_ERR_EE_KEY_TOO_SMALL)
-    {
-        X509_V_ERR_EE_KEY_TOO_SMALL, //66
-        "X509_V_ERR_EE_KEY_TOO_SMALL"
-    },
-#endif
-#if defined(X509_V_ERR_CA_KEY_TOO_SMALL)
-    {
-        X509_V_ERR_CA_KEY_TOO_SMALL, //67
-        "X509_V_ERR_CA_KEY_TOO_SMALL"
-    },
-#endif
-#if defined(X509_V_ERR_CA_MD_TOO_WEAK)
-    {
-        X509_V_ERR_CA_MD_TOO_WEAK, //68
-        "X509_V_ERR_CA_MD_TOO_WEAK"
-    },
-#endif
-#if defined(X509_V_ERR_INVALID_CALL)
-    {
-        X509_V_ERR_INVALID_CALL, //69
-        "X509_V_ERR_INVALID_CALL"
-    },
-#endif
-#if defined(X509_V_ERR_STORE_LOOKUP)
-    {
-        X509_V_ERR_STORE_LOOKUP, //70
-        "X509_V_ERR_STORE_LOOKUP"
-    },
-#endif
-#if defined(X509_V_ERR_NO_VALID_SCTS)
-    {
-        X509_V_ERR_NO_VALID_SCTS, //71
-        "X509_V_ERR_NO_VALID_SCTS"
-    },
-#endif
-#if defined(X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION)
-    {
-        X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION, //72
-        "X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION"
-    },
-#endif
-#if defined(X509_V_ERR_OCSP_VERIFY_NEEDED)
-    {
-        X509_V_ERR_OCSP_VERIFY_NEEDED, //73
-        "X509_V_ERR_OCSP_VERIFY_NEEDED"
-    },
-#endif
-#if defined(X509_V_ERR_OCSP_VERIFY_FAILED)
-    {
-        X509_V_ERR_OCSP_VERIFY_FAILED, //74
-        "X509_V_ERR_OCSP_VERIFY_FAILED"
-    },
-#endif
-#if defined(X509_V_ERR_OCSP_CERT_UNKNOWN)
-    {
-        X509_V_ERR_OCSP_CERT_UNKNOWN, //75
-        "X509_V_ERR_OCSP_CERT_UNKNOWN"
-    },
-#endif
-    { SSL_ERROR_NONE, "SSL_ERROR_NONE"},
-    {SSL_ERROR_NONE, NULL}
-};
-
 static const char *OptionalSslErrors[] = {
     "X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER",
     "X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION",
@@ -475,14 +99,6 @@ static SslErrorAlias TheSslErrorShortcutsArray[] = {
 typedef std::map<std::string, const Security::ErrorCode *> SslErrorShortcuts;
 SslErrorShortcuts TheSslErrorShortcuts;
 
-static void loadSslErrorMap()
-{
-    assert(TheSslErrors.empty());
-    for (int i = 0; TheSslErrorArray[i].name; ++i) {
-        TheSslErrors[TheSslErrorArray[i].value] = &TheSslErrorArray[i];
-    }
-}
-
 static void loadSslErrorShortcutsMap()
 {
     assert(TheSslErrorShortcuts.empty());
@@ -490,16 +106,6 @@ static void loadSslErrorShortcutsMap()
         TheSslErrorShortcuts[TheSslErrorShortcutsArray[i].name] = TheSslErrorShortcutsArray[i].errors;
 }
 
-Security::ErrorCode Ssl::GetErrorCode(const char *name)
-{
-    //TODO: use a std::map?
-    for (int i = 0; TheSslErrorArray[i].name != NULL; ++i) {
-        if (strcmp(name, TheSslErrorArray[i].name) == 0)
-            return TheSslErrorArray[i].value;
-    }
-    return SSL_ERROR_NONE;
-}
-
 bool
 Ssl::ParseErrorString(const char *name, Security::Errors &errors)
 {
@@ -513,7 +119,8 @@ Ssl::ParseErrorString(const char *name, Security::Errors &errors)
 
     if (xisdigit(*name)) {
         const long int value = strtol(name, NULL, 0);
-        if (SQUID_SSL_ERROR_MIN <= value && value <= SQUID_SSL_ERROR_MAX) {
+        if ((SQUID_TLS_ERR_OFFSET < value && value < SQUID_TLS_ERR_END) || // custom
+                (value >= 0)) { // an official error, including SSL_ERROR_NONE
             errors.emplace(value);
             return true;
         }
@@ -537,20 +144,6 @@ Ssl::ParseErrorString(const char *name, Security::Errors &errors)
     return false; // not reached
 }
 
-const char *Ssl::GetErrorName(Security::ErrorCode value, const bool prefixRawCode)
-{
-    if (TheSslErrors.empty())
-        loadSslErrorMap();
-
-    const SslErrors::const_iterator it = TheSslErrors.find(value);
-    if (it != TheSslErrors.end())
-        return it->second->name;
-
-    static char tmpBuffer[128];
-    snprintf(tmpBuffer, sizeof(tmpBuffer), "%s%d", prefixRawCode ? "SSL_ERR=" : "", (int)value);
-    return tmpBuffer;
-}
-
 bool
 Ssl::ErrorIsOptional(const char *name)
 {
@@ -567,248 +160,3 @@ Ssl::GetErrorDescr(Security::ErrorCode value)
     return ErrorDetailsManager::GetInstance().getDefaultErrorDescr(value);
 }
 
-Ssl::ErrorDetail::err_frm_code Ssl::ErrorDetail::ErrorFormatingCodes[] = {
-    {"ssl_subject", &Ssl::ErrorDetail::subject},
-    {"ssl_ca_name", &Ssl::ErrorDetail::ca_name},
-    {"ssl_cn", &Ssl::ErrorDetail::cn},
-    {"ssl_notbefore", &Ssl::ErrorDetail::notbefore},
-    {"ssl_notafter", &Ssl::ErrorDetail::notafter},
-    {"err_name", &Ssl::ErrorDetail::err_code},
-    {"ssl_error_descr", &Ssl::ErrorDetail::err_descr},
-    {"ssl_lib_error", &Ssl::ErrorDetail::err_lib_error},
-    {NULL,NULL}
-};
-
-/**
- * The subject of the current certification in text form
- */
-const char  *Ssl::ErrorDetail::subject() const
-{
-    if (broken_cert.get()) {
-        static char tmpBuffer[256]; // A temporary buffer
-        if (X509_NAME_oneline(X509_get_subject_name(broken_cert.get()), tmpBuffer, sizeof(tmpBuffer))) {
-            // quote to avoid possible html code injection through
-            // certificate subject
-            return html_quote(tmpBuffer);
-        }
-    }
-    return "[Not available]";
-}
-
-// helper function to be used with Ssl::matchX509CommonNames
-static int copy_cn(void *check_data,  ASN1_STRING *cn_data)
-{
-    String *str = (String *)check_data;
-    if (!str) // no data? abort
-        return 0;
-    if (cn_data && cn_data->length) {
-        if (str->size() > 0)
-            str->append(", ");
-        str->append((const char *)cn_data->data, cn_data->length);
-    }
-    return 1;
-}
-
-/**
- * The list with certificates cn and alternate names
- */
-const char *Ssl::ErrorDetail::cn() const
-{
-    if (broken_cert.get()) {
-        static String tmpStr;  ///< A temporary string buffer
-        tmpStr.clean();
-        Ssl::matchX509CommonNames(broken_cert.get(), &tmpStr, copy_cn);
-        if (tmpStr.size()) {
-            // quote to avoid possible html code injection through
-            // certificate subject
-            return html_quote(tmpStr.termedBuf());
-        }
-    }
-    return "[Not available]";
-}
-
-/**
- * The issuer name
- */
-const char *Ssl::ErrorDetail::ca_name() const
-{
-    if (broken_cert.get()) {
-        static char tmpBuffer[256]; // A temporary buffer
-        if (X509_NAME_oneline(X509_get_issuer_name(broken_cert.get()), tmpBuffer, sizeof(tmpBuffer))) {
-            // quote to avoid possible html code injection through
-            // certificate issuer subject
-            return html_quote(tmpBuffer);
-        }
-    }
-    return "[Not available]";
-}
-
-/**
- * The certificate "not before" field
- */
-const char *Ssl::ErrorDetail::notbefore() const
-{
-    if (broken_cert.get()) {
-        if (const auto tm = X509_getm_notBefore(broken_cert.get())) {
-            static char tmpBuffer[256]; // A temporary buffer
-            Ssl::asn1timeToString(tm, tmpBuffer, sizeof(tmpBuffer));
-            return tmpBuffer;
-        }
-    }
-    return "[Not available]";
-}
-
-/**
- * The certificate "not after" field
- */
-const char *Ssl::ErrorDetail::notafter() const
-{
-    if (broken_cert.get()) {
-        if (const auto tm = X509_getm_notAfter(broken_cert.get())) {
-            static char tmpBuffer[256]; // A temporary buffer
-            Ssl::asn1timeToString(tm, tmpBuffer, sizeof(tmpBuffer));
-            return tmpBuffer;
-        }
-    }
-    return "[Not available]";
-}
-
-/**
- * The string representation of the error_no
- */
-const char *Ssl::ErrorDetail::err_code() const
-{
-    // We can use the GetErrorName but using the detailEntry is faster,
-    // so try it first.
-    if (const char *err = detailEntry.name.termedBuf())
-        return err;
-
-    // error details not loaded yet or not defined in error_details.txt,
-    // try the GetErrorName...
-    return GetErrorName(error_no);
-}
-
-/**
- * A short description of the error_no
- */
-const char *Ssl::ErrorDetail::err_descr() const
-{
-    if (error_no == SSL_ERROR_NONE)
-        return "[No Error]";
-    if (const char *err = detailEntry.descr.termedBuf())
-        return err;
-    return "[Not available]";
-}
-
-const char *Ssl::ErrorDetail::err_lib_error() const
-{
-    if (errReason.size() > 0)
-        return errReason.termedBuf();
-    else if (lib_error_no != SSL_ERROR_NONE)
-        return Security::ErrorString(lib_error_no);
-    else
-        return "[No Error]";
-}
-
-/**
- * Converts the code to a string value. Supported formatting codes are:
- *
- * Error meta information:
- * %err_name: The name of a high-level SSL error (e.g., X509_V_ERR_*)
- * %ssl_error_descr: A short description of the SSL error
- * %ssl_lib_error: human-readable low-level error string by Security::ErrorString()
- *
- * Certificate information extracted from broken (not necessarily peer!) cert
- * %ssl_cn: The comma-separated list of common and alternate names
- * %ssl_subject: The certificate subject
- * %ssl_ca_name: The certificate issuer name
- * %ssl_notbefore: The certificate "not before" field
- * %ssl_notafter: The certificate "not after" field
- *
- \retval  the length of the code (the number of characters will be replaced by value)
-*/
-int Ssl::ErrorDetail::convert(const char *code, const char **value) const
-{
-    *value = "-";
-    for (int i=0; ErrorFormatingCodes[i].code!=NULL; ++i) {
-        const int len = strlen(ErrorFormatingCodes[i].code);
-        if (strncmp(code,ErrorFormatingCodes[i].code, len)==0) {
-            ErrorDetail::fmt_action_t action  = ErrorFormatingCodes[i].fmt_action;
-            *value = (this->*action)();
-            return len;
-        }
-    }
-    // TODO: Support logformat %codes.
-    return 0;
-}
-
-/**
- * It uses the convert method to build the string errDetailStr using
- * a template message for the current SSL error. The template messages
- * can also contain normal error pages formatting codes.
- * Currently the error template messages are hard-coded
- */
-void Ssl::ErrorDetail::buildDetail() const
-{
-    char const *s = NULL;
-    char const *p;
-    char const *t;
-    int code_len = 0;
-
-    if (ErrorDetailsManager::GetInstance().getErrorDetail(error_no, request, detailEntry))
-        s = detailEntry.detail.termedBuf();
-
-    if (!s)
-        s = SslErrorDetailDefaultStr;
-
-    assert(s);
-    while ((p = strchr(s, '%'))) {
-        errDetailStr.append(s, p - s);
-        code_len = convert(++p, &t);
-        if (code_len)
-            errDetailStr.append(t);
-        else
-            errDetailStr.append("%");
-        s = p + code_len;
-    }
-    errDetailStr.append(s, strlen(s));
-}
-
-const String &Ssl::ErrorDetail::toString() const
-{
-    if (errDetailStr.size() == 0)
-        buildDetail();
-    return errDetailStr;
-}
-
-Ssl::ErrorDetail::ErrorDetail( Security::ErrorCode err_no, X509 *cert, X509 *broken, const char *aReason): error_no (err_no), lib_error_no(SSL_ERROR_NONE), errReason(aReason)
-{
-    if (cert)
-        peer_cert.resetAndLock(cert);
-
-    if (broken)
-        broken_cert.resetAndLock(broken);
-    else
-        broken_cert.resetAndLock(cert);
-
-    detailEntry.error_no = SSL_ERROR_NONE;
-}
-
-Ssl::ErrorDetail::ErrorDetail(Ssl::ErrorDetail const &anErrDetail)
-{
-    error_no = anErrDetail.error_no;
-    request = anErrDetail.request;
-
-    if (anErrDetail.peer_cert.get()) {
-        peer_cert.resetAndLock(anErrDetail.peer_cert.get());
-    }
-
-    if (anErrDetail.broken_cert.get()) {
-        broken_cert.resetAndLock(anErrDetail.broken_cert.get());
-    }
-
-    detailEntry = anErrDetail.detailEntry;
-
-    lib_error_no = anErrDetail.lib_error_no;
-}
-
index 4fc51d187f4373579858ee2e62df53f47eca5c99..02579dfb35b9d907b5733743583dc4b2766b0046 100644 (file)
@@ -9,10 +9,10 @@
 #ifndef _SQUID_SSL_ERROR_DETAIL_H
 #define _SQUID_SSL_ERROR_DETAIL_H
 
-#include "err_detail_type.h"
-#include "ErrorDetailManager.h"
-#include "HttpRequest.h"
-#include "security/forward.h"
+#include "security/ErrorDetail.h"
+
+// TODO: Remove Security::X wrappers and move the remaining configurable error
+// details (i.e. templates/error-details.txt) code to src/security/ErrorDetail.
 
 namespace Ssl
 {
@@ -24,11 +24,19 @@ namespace Ssl
 bool ParseErrorString(const char *name, Security::Errors &);
 
 /// The Security::ErrorCode code of the error described by  "name".
-Security::ErrorCode GetErrorCode(const char *name);
+inline Security::ErrorCode
+GetErrorCode(const char *name)
+{
+    return Security::ErrorCodeFromName(name);
+}
 
 /// \return string representation of a known TLS error (or a raw error code)
 /// \param prefixRawCode whether to prefix raw codes with "SSL_ERR="
-const char *GetErrorName(Security::ErrorCode value, const bool prefixRawCode = false);
+inline const char *
+GetErrorName(const Security::ErrorCode code, const bool prefixRawCode = false)
+{
+    return Security::ErrorNameFromCode(code, prefixRawCode);
+}
 
 /// A short description of the TLS error "value"
 const char *GetErrorDescr(Security::ErrorCode value);
@@ -36,63 +44,6 @@ const char *GetErrorDescr(Security::ErrorCode value);
 /// \return true if the TLS error is optional and may not be supported by current squid version
 bool ErrorIsOptional(const char *name);
 
-/**
- * Used to pass SSL error details to the error pages returned to the
- * end user.
- */
-class ErrorDetail
-{
-public:
-    // if broken certificate is nil, the peer certificate is broken
-    ErrorDetail(Security::ErrorCode err_no, X509 *peer, X509 *broken, const char *aReason = NULL);
-    ErrorDetail(ErrorDetail const &);
-    const String &toString() const;  ///< An error detail string to embed in squid error pages
-    void useRequest(HttpRequest *aRequest) { if (aRequest != NULL) request = aRequest;}
-    /// The error name to embed in squid error pages
-    const char *errorName() const {return err_code();}
-    /// The error no
-    Security::ErrorCode errorNo() const {return error_no;}
-    ///Sets the low-level error returned by OpenSSL ERR_get_error()
-    void setLibError(unsigned long lib_err_no) {lib_error_no = lib_err_no;}
-    /// the peer certificate
-    X509 *peerCert() { return peer_cert.get(); }
-    /// peer or intermediate certificate that failed validation
-    X509 *brokenCert() {return broken_cert.get(); }
-private:
-    typedef const char * (ErrorDetail::*fmt_action_t)() const;
-    /**
-     * Holds a formatting code and its conversion method
-     */
-    class err_frm_code
-    {
-    public:
-        const char *code;             ///< The formatting code
-        fmt_action_t fmt_action; ///< A pointer to the conversion method
-    };
-    static err_frm_code  ErrorFormatingCodes[]; ///< The supported formatting codes
-
-    const char *subject() const;
-    const char *ca_name() const;
-    const char *cn() const;
-    const char *notbefore() const;
-    const char *notafter() const;
-    const char *err_code() const;
-    const char *err_descr() const;
-    const char *err_lib_error() const;
-
-    int convert(const char *code, const char **value) const;
-    void buildDetail() const;
-
-    mutable String errDetailStr; ///< Caches the error detail message
-    Security::ErrorCode error_no;   ///< The error code
-    unsigned long lib_error_no; ///< low-level error returned by OpenSSL ERR_get_error(3SSL)
-    Security::CertPointer peer_cert; ///< A pointer to the peer certificate
-    Security::CertPointer broken_cert; ///< A pointer to the broken certificate (peer or intermediate)
-    String errReason; ///< A custom reason for error, else retrieved from OpenSSL.
-    mutable ErrorDetailEntry detailEntry;
-    HttpRequest::Pointer request;
-};
-
 }//namespace Ssl
 #endif
 
index 435cfc66444fdc99ed429f095c87e9aa4486480c..8bb6e5a7eaadb487bb8e894e3073630c3e475485 100644 (file)
@@ -25,7 +25,7 @@ namespace Ssl
 class ErrorDetailEntry
 {
 public:
-    Security::ErrorCode error_no; ///< The SSL error code
+    Security::ErrorCode error_no = 0; ///< TLS error; \see Security::ErrorCode
     String name; ///< a name for the error
     String detail; ///< for error page %D macro expansion; may contain macros
     String descr;  ///< short error description (for use in debug messages or error pages)
index e48e39b35b174f62c82286eef9a07f92b1c1c6a1..5ea4e4ba91b9cf0212147cb48eba8b4a11164b33 100644 (file)
@@ -15,6 +15,7 @@
 #include "fde.h"
 #include "http/Stream.h"
 #include "HttpRequest.h"
+#include "security/ErrorDetail.h"
 #include "security/NegotiationHistory.h"
 #include "SquidConfig.h"
 #include "ssl/bio.h"
@@ -222,8 +223,9 @@ Ssl::PeekingPeerConnector::noteNegotiationDone(ErrorState *error)
     if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) {
         if (!serverBump->serverCert.get()) {
             // remember the server certificate from the ErrorDetail object
-            if (error && error->detail && error->detail->peerCert())
-                serverBump->serverCert.resetAndLock(error->detail->peerCert());
+            const auto errDetail = dynamic_cast<Security::ErrorDetail *>(error ? error->detail.getRaw() : nullptr);
+            if (errDetail && errDetail->peerCert())
+                serverBump->serverCert.resetAndLock(errDetail->peerCert());
             else {
                 handleServerCertificate();
             }
@@ -292,7 +294,7 @@ Ssl::PeekingPeerConnector::noteWantWrite()
 }
 
 void
-Ssl::PeekingPeerConnector::noteNegotiationError(const int result, const int ssl_error, const int ssl_lib_error)
+Ssl::PeekingPeerConnector::noteNegotiationError(const Security::ErrorDetailPointer &errorDetail)
 {
     const int fd = serverConnection()->fd;
     Security::SessionPointer session(fd_table[fd].ssl);
@@ -340,14 +342,14 @@ Ssl::PeekingPeerConnector::noteNegotiationError(const int result, const int ssl_
             (srvBio->bumpMode() == Ssl::bumpPeek  || srvBio->bumpMode() == Ssl::bumpStare) && srvBio->holdWrite()) {
         Security::CertPointer serverCert(SSL_get_peer_certificate(session.get()));
         if (serverCert) {
-            debugs(81, 3, "Error ("  << Security::ErrorString(ssl_lib_error) <<  ") but, hold write on SSL connection on FD " << fd);
+            debugs(81, 3, "hold TLS write on FD " << fd << " despite " << errorDetail);
             checkForPeekAndSplice();
             return;
         }
     }
 
     // else call parent noteNegotiationError to produce an error page
-    Security::PeerConnector::noteNegotiationError(result, ssl_error, ssl_lib_error);
+    Security::PeerConnector::noteNegotiationError(errorDetail);
 }
 
 void
index 1987db83334cfff281e82b6569c2dc43294c3774..41cf0b882501aafaf0defa188e56e6d18cfab9f7 100644 (file)
@@ -39,7 +39,7 @@ public:
     virtual bool initialize(Security::SessionPointer &);
     virtual Security::ContextPointer getTlsContext();
     virtual void noteWantWrite();
-    virtual void noteNegotiationError(const int result, const int ssl_error, const int ssl_lib_error);
+    virtual void noteNegotiationError(const Security::ErrorDetailPointer &);
     virtual void noteNegotiationDone(ErrorState *error);
 
     /// Updates associated client connection manager members
index 602d1b10588e647ee1696ea20dace26460969ec9..4e4f2d1b62d42f3ff67c5f41aed543f4875468b5 100644 (file)
@@ -218,44 +218,12 @@ Ssl::CertValidationResponse::getError(int errorId)
     return errors.back();
 }
 
-Ssl::CertValidationResponse::RecvdError::RecvdError(const RecvdError &old)
-{
-    id = old.id;
-    error_no = old.error_no;
-    error_reason = old.error_reason;
-    error_depth = old.error_depth;
-    setCert(old.cert.get());
-}
-
-Ssl::CertValidationResponse::RecvdError & Ssl::CertValidationResponse::RecvdError::operator = (const RecvdError &old)
-{
-    id = old.id;
-    error_no = old.error_no;
-    error_reason = old.error_reason;
-    error_depth = old.error_depth;
-    setCert(old.cert.get());
-    return *this;
-}
-
 void
 Ssl::CertValidationResponse::RecvdError::setCert(X509 *aCert)
 {
     cert.resetAndLock(aCert);
 }
 
-Ssl::CertValidationMsg::CertItem::CertItem(const CertItem &old)
-{
-    name = old.name;
-    setCert(old.cert.get());
-}
-
-Ssl::CertValidationMsg::CertItem & Ssl::CertValidationMsg::CertItem::operator = (const CertItem &old)
-{
-    name = old.name;
-    setCert(old.cert.get());
-    return *this;
-}
-
 void
 Ssl::CertValidationMsg::CertItem::setCert(X509 *aCert)
 {
index 58c8f51bd8501cd73a3a5e460004539d75b9e3e0..25905a72fb16aeacf60b9a3108c1e7de962b180f 100644 (file)
@@ -47,15 +47,12 @@ public:
     class  RecvdError
     {
     public:
-        RecvdError(): id(0), error_no(SSL_ERROR_NONE), cert(NULL), error_depth(-1) {}
-        RecvdError(const RecvdError &);
-        RecvdError & operator =(const RecvdError &);
         void setCert(X509 *);  ///< Sets cert to the given certificate
-        int id; ///<  The id of the error
-        Security::ErrorCode error_no; ///< The OpenSSL error code
+        int id = 0; ///<  The id of the error
+        Security::ErrorCode error_no = 0; ///< The OpenSSL error code
         std::string error_reason; ///< A string describing the error
         Security::CertPointer cert; ///< The broken certificate
-        int error_depth; ///< The error depth
+        int error_depth = -1; ///< The error depth
     };
 
     typedef std::vector<RecvdError> RecvdErrors;
@@ -92,9 +89,6 @@ private:
     public:
         std::string name; ///< The certificate Id to use
         Security::CertPointer cert;       ///< A pointer to certificate
-        CertItem(): cert(NULL) {}
-        CertItem(const CertItem &);
-        CertItem & operator =(const CertItem &);
         void setCert(X509 *); ///< Sets cert to the given certificate
     };
 
index b290a4f76daf06fc8dd625063d36bc3effcfbd70..413cb366f5156809f4a50305192101b6769791ca 100644 (file)
@@ -24,6 +24,7 @@
 #include "globals.h"
 #include "ipc/MemMap.h"
 #include "security/CertError.h"
+#include "security/ErrorDetail.h"
 #include "security/Session.h"
 #include "SquidConfig.h"
 #include "SquidTime.h"
@@ -379,11 +380,12 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
                 broken_cert.resetAndLock(last_used_cert);
         }
 
-        auto *errDetail = new Ssl::ErrorDetail(error_no, peer_cert.get(), broken_cert.get());
-        if (!SSL_set_ex_data(ssl, ssl_ex_index_ssl_error_detail, errDetail)) {
-            debugs(83, 2, "Failed to set Ssl::ErrorDetail in ssl_verify_cb: Certificate " << buffer);
-            delete errDetail;
-        }
+        std::unique_ptr<Security::ErrorDetail::Pointer> edp(new Security::ErrorDetail::Pointer(
+                    new Security::ErrorDetail(error_no, peer_cert, broken_cert)));
+        if (SSL_set_ex_data(ssl, ssl_ex_index_ssl_error_detail, edp.get()))
+            edp.release();
+        else
+            debugs(83, 2, "failed to store a " << buffer << " error detail: " << *edp);
     }
 
     return ok;
@@ -453,7 +455,7 @@ static void
 ssl_free_ErrorDetail(void *, void *ptr, CRYPTO_EX_DATA *,
                      int, long, void *)
 {
-    Ssl::ErrorDetail  *errDetail = static_cast <Ssl::ErrorDetail *>(ptr);
+    const auto errDetail = static_cast<Security::ErrorDetail::Pointer*>(ptr);
     delete errDetail;
 }
 
@@ -521,7 +523,7 @@ Ssl::Initialize(void)
             fatalf("Unable to find SSL engine '%s'\n", ::Config.SSL.ssl_engine);
 
         if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)) {
-            const int ssl_error = ERR_get_error();
+            const auto ssl_error = ERR_get_error();
             fatalf("Failed to initialise SSL engine: %s\n", Security::ErrorString(ssl_error));
         }
     }
@@ -566,7 +568,7 @@ Ssl::InitClientContext(Security::ContextPointer &ctx, Security::PeerOptions &pee
 
         const char *cipher = peer.sslCipher.c_str();
         if (!SSL_CTX_set_cipher_list(ctx.get(), cipher)) {
-            const int ssl_error = ERR_get_error();
+            const auto ssl_error = ERR_get_error();
             fatalf("Failed to set SSL cipher suite '%s': %s\n",
                    cipher, Security::ErrorString(ssl_error));
         }
@@ -580,7 +582,7 @@ Ssl::InitClientContext(Security::ContextPointer &ctx, Security::PeerOptions &pee
 
             const char *certfile = keys.certFile.c_str();
             if (!SSL_CTX_use_certificate_chain_file(ctx.get(), certfile)) {
-                const int ssl_error = ERR_get_error();
+                const auto ssl_error = ERR_get_error();
                 fatalf("Failed to acquire SSL certificate '%s': %s\n",
                        certfile, Security::ErrorString(ssl_error));
             }
@@ -590,7 +592,7 @@ Ssl::InitClientContext(Security::ContextPointer &ctx, Security::PeerOptions &pee
             ssl_ask_password(ctx.get(), keyfile);
 
             if (!SSL_CTX_use_PrivateKey_file(ctx.get(), keyfile, SSL_FILETYPE_PEM)) {
-                const int ssl_error = ERR_get_error();
+                const auto ssl_error = ERR_get_error();
                 fatalf("Failed to acquire SSL private key '%s': %s\n",
                        keyfile, Security::ErrorString(ssl_error));
             }
@@ -598,7 +600,7 @@ Ssl::InitClientContext(Security::ContextPointer &ctx, Security::PeerOptions &pee
             debugs(83, 5, "Comparing private and public SSL keys.");
 
             if (!SSL_CTX_check_private_key(ctx.get())) {
-                const int ssl_error = ERR_get_error();
+                const auto ssl_error = ERR_get_error();
                 fatalf("SSL private key '%s' does not match public key '%s': %s\n",
                        certfile, keyfile, Security::ErrorString(ssl_error));
             }
@@ -825,7 +827,7 @@ Ssl::chainCertificatesToSSLContext(Security::ContextPointer &ctx, Security::Serv
         // increase the certificate lock
         X509_up_ref(signingCert);
     } else {
-        const int ssl_error = ERR_get_error();
+        const auto ssl_error = ERR_get_error();
         debugs(33, DBG_IMPORTANT, "WARNING: can not add signing certificate to SSL context chain: " << Security::ErrorString(ssl_error));
     }
 
@@ -926,7 +928,7 @@ Ssl::setClientSNI(SSL *ssl, const char *fqdn)
     // if the TLS servername extension (SNI) is enabled in openssl library.
 #if defined(SSL_CTRL_SET_TLSEXT_HOSTNAME)
     if (!SSL_set_tlsext_host_name(ssl, fqdn)) {
-        const int ssl_error = ERR_get_error();
+        const auto ssl_error = ERR_get_error();
         debugs(83, 3,  "WARNING: unable to set TLS servername extension (SNI): " <<
                Security::ErrorString(ssl_error) << "\n");
     }
@@ -1039,7 +1041,7 @@ issuerExistInCaDb(X509 *cert, const Security::ContextPointer &connContext)
         if (issuer)
             X509_free(issuer);
     } else {
-        const int ssl_error = ERR_get_error();
+        const auto ssl_error = ERR_get_error();
         debugs(83, DBG_IMPORTANT, "Failed to initialize STORE_CTX object: " << Security::ErrorString(ssl_error));
     }
     X509_STORE_CTX_free(storeCtx);
index f70bcf2b67e5f1772061516dd7ff912116597311..30833874a3ddc2ac6261d4e16e2653d8aa9c09cb 100644 (file)
  \ingroup ServerProtocol
  */
 
-// Custom SSL errors; assumes all official errors are positive
-#define SQUID_X509_V_ERR_INFINITE_VALIDATION -4
-#define SQUID_X509_V_ERR_CERT_CHANGE -3
-#define SQUID_ERR_SSL_HANDSHAKE -2
-#define SQUID_X509_V_ERR_DOMAIN_MISMATCH -1
-// All SSL errors range: from smallest (negative) custom to largest SSL error
-#define SQUID_SSL_ERROR_MIN SQUID_X509_V_ERR_CERT_CHANGE
-#define SQUID_SSL_ERROR_MAX INT_MAX
-
 // Maximum certificate validation callbacks. OpenSSL versions exceeding this
 // limit are deemed stuck in an infinite validation loop (OpenSSL bug #3090)
 // and will trigger the SQUID_X509_V_ERR_INFINITE_VALIDATION error.
@@ -75,7 +66,6 @@ int AskPasswordCb(char *buf, int size, int rwflag, void *userdata);
 /// call before generating any SSL context
 void Initialize();
 
-class ErrorDetail;
 class CertValidationResponse;
 typedef RefCount<CertValidationResponse> CertValidationResponsePointer;
 
index 0e36a9ee30147ba97fa613b96b9fefdccdcb0519..829f436c266be2936cdc5469f4d75fada5698758 100644 (file)
@@ -34,7 +34,6 @@ void HttpRequest::adaptHistoryImport(const HttpRequest &) STUB
 Adaptation::Icap::History::Pointer HttpRequest::icapHistory() const STUB_RETVAL(Adaptation::Icap::History::Pointer())
 #endif
 void HttpRequest::recordLookup(const Dns::LookupDetails &) STUB
-void HttpRequest::detailError(err_type, int) STUB
 void HttpRequest::clearError() STUB
 void HttpRequest::clean() STUB
 void HttpRequest::init() STUB
diff --git a/src/tests/stub_liberror.cc b/src/tests/stub_liberror.cc
new file mode 100644 (file)
index 0000000..331289f
--- /dev/null
@@ -0,0 +1,14 @@
+#include "squid.h"
+#include "error/Error.h"
+#include "sbuf/SBuf.h"
+
+#define STUB_API "error/liberror.la"
+#include "tests/STUB.h"
+
+const char * err_type_str[ERR_MAX] = {};
+
+void Error::update(const Error &) STUB_NOP
+
+std::ostream &operator <<(std::ostream &os, const Error &) STUB_RETVAL(os)
+
+ErrorDetail::Pointer MakeNamedErrorDetail(const char *name) STUB_RETVAL(ErrorDetail::Pointer())
index b5475018a5d8978f848b42e306f0b4229b672e4a..89bd9611b0909a112491f1414dea39e671a10dae 100644 (file)
@@ -118,7 +118,7 @@ void Stream::buildRangeHeader(HttpReply *) STUB
 clientStreamNode *Stream::getTail() const STUB_RETVAL(nullptr)
 clientStreamNode *Stream::getClientReplyContext() const STUB_RETVAL(nullptr)
 ConnStateData *Stream::getConn() const STUB_RETVAL(nullptr)
-void Stream::noteIoError(const int) STUB
+void Stream::noteIoError(const Error &, const LogTagsErrors &) STUB
 void Stream::finished() STUB
 void Stream::initiateClose(const char *) STUB
 void Stream::deferRecipientForLater(clientStreamNode *, HttpReply *, StoreIOBuffer) STUB
index 10793dbbe608827805f9cc3a38c013c71bca3f1b..6a2e2091a176f64438b84a6865746133e6a32f3b 100644 (file)
@@ -31,12 +31,30 @@ std::ostream &Security::operator <<(std::ostream &os, const Security::EncryptorA
 Security::HandshakeParser::HandshakeParser(MessageSource) STUB
 bool Security::HandshakeParser::parseHello(const SBuf &) STUB_RETVAL(false)
 
+#include "security/Io.h"
+Security::IoResult Security::Accept(Comm::Connection &) STUB_RETVAL(IoResult(IoResult::ioError))
+Security::IoResult Security::Connect(Comm::Connection &) STUB_RETVAL(IoResult(IoResult::ioError))
+void Security::ForgetErrors() STUB
+
 #include "security/KeyData.h"
 namespace Security
 {
 void KeyData::loadFromFiles(const AnyP::PortCfg &, const char *) STUB
 }
 
+#include "security/ErrorDetail.h"
+Security::ErrorDetail::ErrorDetail(ErrorCode, const CertPointer &, const CertPointer &, const char *) STUB
+#if USE_OPENSSL
+Security::ErrorDetail::ErrorDetail(ErrorCode, int, int) STUB
+#elif USE_GNUTLS
+Security::ErrorDetail::ErrorDetail(ErrorCode, LibErrorCode, int) STUB
+#endif
+void Security::ErrorDetail::setPeerCertificate(const CertPointer &) STUB
+SBuf Security::ErrorDetail::verbose(const HttpRequestPointer &) const STUB_RETVAL(SBuf())
+SBuf Security::ErrorDetail::brief() const STUB_RETVAL(SBuf())
+Security::ErrorCode Security::ErrorCodeFromName(const char *) STUB_RETVAL(0)
+const char *Security::ErrorNameFromCode(ErrorCode, bool) STUB_RETVAL("")
+
 #include "security/NegotiationHistory.h"
 Security::NegotiationHistory::NegotiationHistory() STUB
 void Security::NegotiationHistory::retrieveNegotiatedInfo(const Security::SessionPointer &) STUB
@@ -62,7 +80,7 @@ bool PeerConnector::sslFinalized() STUB_RETVAL(false)
 void PeerConnector::handleNegotiateError(const int) STUB
 void PeerConnector::noteWantRead() STUB
 void PeerConnector::noteWantWrite() STUB
-void PeerConnector::noteNegotiationError(const int, const int, const int) STUB
+void PeerConnector::noteNegotiationError(const Security::ErrorDetailPointer &) STUB
 //    virtual Security::ContextPointer getTlsContext() = 0;
 void PeerConnector::bail(ErrorState *) STUB
 void PeerConnector::sendSuccess() STUB
index 27662d2c3910092444ee5c1af4afddb8bd7b1d62..6b0336fa10d97dcd9042e329faded1de39a563a8 100644 (file)
@@ -44,10 +44,6 @@ void Ssl::GlobalContextStorage::reconfigureStart() STUB
 
 #include "ssl/ErrorDetail.h"
 Security::ErrorCode parseErrorString(const char *name) STUB_RETVAL(0)
-//const char *Ssl::getErrorName(Security::ErrorCode value) STUB_RETVAL(NULL)
-Ssl::ErrorDetail::ErrorDetail(Security::ErrorCode, X509 *, X509 *, const char *) STUB
-Ssl::ErrorDetail::ErrorDetail(ErrorDetail const &) STUB
-const String & Ssl::ErrorDetail::toString() const STUB_RETSTATREF(String)
 
 #include "ssl/support.h"
 namespace Ssl