From: Christos Tsantilas Date: Thu, 10 Dec 2020 20:12:45 +0000 (+0000) Subject: Detail client closures of CONNECT tunnels during TLS handshake (#691) X-Git-Tag: 4.15-20210522-snapshot~39 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=83b053a081c0669e510ebf41295d4b4f5ea7cc97;p=thirdparty%2Fsquid.git Detail client closures of CONNECT tunnels during TLS handshake (#691) ... 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 --- diff --git a/configure.ac b/configure.ac index 5409704c6a..2396cd74ba 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/doc/debug-sections.txt b/doc/debug-sections.txt index e51cfa3978..4824d165ca 100644 --- a/doc/debug-sections.txt +++ b/doc/debug-sections.txt @@ -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 diff --git a/errors/templates/error-details.txt b/errors/templates/error-details.txt index ef46b0928e..881add9909 100644 --- a/errors/templates/error-details.txt +++ b/errors/templates/error-details.txt @@ -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" diff --git a/scripts/calc-must-ids.sh b/scripts/calc-must-ids.sh index 80dbf5a0a1..c39558812c 100755 --- a/scripts/calc-must-ids.sh +++ b/scripts/calc-must-ids.sh @@ -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 diff --git a/scripts/source-maintenance.sh b/scripts/source-maintenance.sh index 47e25f892f..163a827c48 100755 --- a/scripts/source-maintenance.sh +++ b/scripts/source-maintenance.sh @@ -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 diff --git a/src/AccessLogEntry.cc b/src/AccessLogEntry.cc index 9cb39ea6b8..9d9bf06e54 100644 --- a/src/AccessLogEntry.cc +++ b/src/AccessLogEntry.cc @@ -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 { diff --git a/src/AccessLogEntry.h b/src/AccessLogEntry.h index 28fe63c67a..65d1f61e13 100644 --- a/src/AccessLogEntry.h +++ b/src/AccessLogEntry.h @@ -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_; diff --git a/src/FwdState.cc b/src/FwdState.cc index 5be266720f..fda7ff494f 100644 --- a/src/FwdState.cc +++ b/src/FwdState.cc @@ -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" diff --git a/src/FwdState.h b/src/FwdState.h index dafece9382..a23d0bf80c 100644 --- a/src/FwdState.h +++ b/src/FwdState.h @@ -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 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); diff --git a/src/HttpRequest.cc b/src/HttpRequest.cc index 6cfc7859a8..3ad30666c9 100644 --- a/src/HttpRequest.cc +++ b/src/HttpRequest.cc @@ -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 diff --git a/src/HttpRequest.h b/src/HttpRequest.h index 57fb1b4bea..de36fdbfe5 100644 --- a/src/HttpRequest.h +++ b/src/HttpRequest.h @@ -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 */ diff --git a/src/LogTags.cc b/src/LogTags.cc index 49d0579241..f0f808f3d3 100644 --- a/src/LogTags.cc +++ b/src/LogTags.cc @@ -10,6 +10,16 @@ #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", diff --git a/src/LogTags.h b/src/LogTags.h index e5732765ff..5dd661cacd 100644 --- a/src/LogTags.h +++ b/src/LogTags.h @@ -11,6 +11,21 @@ #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 diff --git a/src/Makefile.am b/src/Makefile.am index f6d4c568b8..04632e7936 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/Pipeline.cc b/src/Pipeline.cc index 2443a5028e..eabd65a5e0 100644 --- a/src/Pipeline.cc +++ b/src/Pipeline.cc @@ -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) { diff --git a/src/Pipeline.h b/src/Pipeline.h index 86503079b6..6358cd7237 100644 --- a/src/Pipeline.h +++ b/src/Pipeline.h @@ -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 &); diff --git a/src/acl/AclDenyInfoList.h b/src/acl/AclDenyInfoList.h index 003030df17..5ae5decd66 100644 --- a/src/acl/AclDenyInfoList.h +++ b/src/acl/AclDenyInfoList.h @@ -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" diff --git a/src/acl/FilledChecklist.h b/src/acl/FilledChecklist.h index 838dd72ead..a4ebb2d1e4 100644 --- a/src/acl/FilledChecklist.h +++ b/src/acl/FilledChecklist.h @@ -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" diff --git a/src/acl/Gadgets.h b/src/acl/Gadgets.h index f85628a21d..c6e6cae8a1 100644 --- a/src/acl/Gadgets.h +++ b/src/acl/Gadgets.h @@ -10,7 +10,7 @@ #define SQUID_ACL_GADGETS_H #include "acl/forward.h" -#include "err_type.h" +#include "error/forward.h" #include diff --git a/src/acl/SquidError.cc b/src/acl/SquidError.cc index 66169cebf6..3ec3cdffb7 100644 --- a/src/acl/SquidError.cc +++ b/src/acl/SquidError.cc @@ -17,7 +17,7 @@ ACLSquidErrorStrategy::match (ACLData * &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; } diff --git a/src/acl/SquidError.h b/src/acl/SquidError.h index 484ab193da..56c9a65320 100644 --- a/src/acl/SquidError.h +++ b/src/acl/SquidError.h @@ -10,7 +10,7 @@ #define SQUID_ACLSQUIDERROR_H #include "acl/Strategy.h" -#include "err_type.h" +#include "error/forward.h" class ACLSquidErrorStrategy : public ACLStrategy { diff --git a/src/acl/SquidErrorData.cc b/src/acl/SquidErrorData.cc index 14603a6f73..a1c9a8e252 100644 --- a/src/acl/SquidErrorData.cc +++ b/src/acl/SquidErrorData.cc @@ -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" diff --git a/src/acl/SquidErrorData.h b/src/acl/SquidErrorData.h index 9919a1c9a3..0806cb5623 100644 --- a/src/acl/SquidErrorData.h +++ b/src/acl/SquidErrorData.h @@ -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 diff --git a/src/adaptation/icap/ModXact.cc b/src/adaptation/icap/ModXact.cc index a73f32c547..bb4da5d91f 100644 --- a/src/adaptation/icap/ModXact.cc +++ b/src/adaptation/icap/ModXact.cc @@ -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(&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(adapted.header); // if no adapted request, update virgin (and inherit its properties later) diff --git a/src/adaptation/icap/ModXact.h b/src/adaptation/icap/ModXact.h index d6c541bdb3..5abbc848ba 100644 --- a/src/adaptation/icap/ModXact.h +++ b/src/adaptation/icap/ModXact.h @@ -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 diff --git a/src/adaptation/icap/Xaction.cc b/src/adaptation/icap/Xaction.cc index b1227dfee9..c904bc7dd6 100644 --- a/src/adaptation/icap/Xaction.cc +++ b/src/adaptation/icap/Xaction.cc @@ -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"); } diff --git a/src/adaptation/icap/Xaction.h b/src/adaptation/icap/Xaction.h index 03abd200e6..1f6cd165e1 100644 --- a/src/adaptation/icap/Xaction.h +++ b/src/adaptation/icap/Xaction.h @@ -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(); diff --git a/src/base/TextException.cc b/src/base/TextException.cc index d6d84164e8..257ca20f8e 100644 --- a/src/base/TextException.cc +++ b/src/base/TextException.cc @@ -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(); } diff --git a/src/base/TextException.h b/src/base/TextException.h index edbcf664ac..2a43fac358 100644 --- a/src/base/TextException.h +++ b/src/base/TextException.h @@ -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()) diff --git a/src/client_side.cc b/src/client_side.cc index 4aed1a53e0..f64e0041aa 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -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" @@ -105,6 +106,8 @@ #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 TimeoutDialer; + AsyncCall::Pointer callback = JobCallback(33, 5, TimeoutDialer, this, ConnStateData::requestTimeout); + commSetConnTimeout(clientConnection, timeout, callback); +} + +void +ConnStateData::extendLifetime() +{ + typedef CommCbMemFunT 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(&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 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 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(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 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 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 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 <e) { - // 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 diff --git a/src/client_side.h b/src/client_side.h index e3e99661ed..b4a0bdd693 100644 --- a/src/client_side.h +++ b/src/client_side.h @@ -11,9 +11,11 @@ #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 ¶ms); + void lifetimeTimeout(const CommTimeoutCbParams ¶ms); // 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 diff --git a/src/client_side_request.cc b/src/client_side_request.cc index cde14e5e17..db24bba323 100644 --- a/src/client_side_request.cc +++ b/src/client_side_request.cc @@ -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 diff --git a/src/client_side_request.h b/src/client_side_request.h index 8b9a548490..e52568f3a8 100644 --- a/src/client_side_request.h +++ b/src/client_side_request.h @@ -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); diff --git a/src/clients/Client.cc b/src/clients/Client.cc index 476dcbc60e..dfb13d053c 100644 --- a/src/clients/Client.cc +++ b/src/clients/Client.cc @@ -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); diff --git a/src/clients/FtpClient.cc b/src/clients/FtpClient.cc index 05a3ee7151..bff53e7d4e 100644 --- a/src/clients/FtpClient.cc +++ b/src/clients/FtpClient.cc @@ -17,10 +17,13 @@ #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() } diff --git a/src/clients/FtpClient.h b/src/clients/FtpClient.h index a76a5a0cd5..32bd4c0f82 100644 --- a/src/clients/FtpClient.h +++ b/src/clients/FtpClient.h @@ -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 diff --git a/src/clients/FtpGateway.cc b/src/clients/FtpGateway.cc index 4aba041b13..e54d1f8fa5 100644 --- a/src/clients/FtpGateway.cc +++ b/src/clients/FtpGateway.cc @@ -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()); diff --git a/src/clients/FtpRelay.cc b/src/clients/FtpRelay.cc index 19967b3e62..191e9467bb 100644 --- a/src/clients/FtpRelay.cc +++ b/src/clients/FtpRelay.cc @@ -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 diff --git a/src/comm/Read.cc b/src/comm/Read.cc index 029347d720..037010692d 100644 --- a/src/comm/Read.cc +++ b/src/comm/Read.cc @@ -101,6 +101,7 @@ Comm::ReadNow(CommIoCbParams ¶ms, 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 ¶ms, 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 index 948540d36e..0000000000 --- a/src/err_detail_type.h +++ /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 index 0000000000..4e30318b4b --- /dev/null +++ b/src/error/Detail.cc @@ -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 index 0000000000..84cb879cda --- /dev/null +++ b/src/error/Detail.h @@ -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 index 0000000000..3ad637d8eb --- /dev/null +++ b/src/error/Error.cc @@ -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 index 0000000000..15ce4868fa --- /dev/null +++ b/src/error/Error.h @@ -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 + +/// 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 index 0000000000..e802bda790 --- /dev/null +++ b/src/error/ExceptionErrorDetail.h @@ -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 index 0000000000..3510531162 --- /dev/null +++ b/src/error/Makefile.am @@ -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 index 0000000000..765d3f2d02 --- /dev/null +++ b/src/error/SysErrorDetail.h @@ -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 */ + diff --git a/src/err_type.h b/src/error/forward.h similarity index 84% rename from src/err_type.h rename to src/error/forward.h index d84ffe51f4..981fabf95b 100644 --- a/src/err_type.h +++ b/src/error/forward.h @@ -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 ErrorDetailPointer; + +#endif /* _SQUID_SRC_ERROR_FORWARD_H */ diff --git a/src/errorpage.cc b/src/errorpage.cc index b3e98154f9..50706788e9 100644 --- a/src/errorpage.cc +++ b/src/errorpage.cc @@ -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; diff --git a/src/errorpage.h b/src/errorpage.h index f8cec6ccda..18c3dfa4c6 100644 --- a/src/errorpage.h +++ b/src/errorpage.h @@ -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_; diff --git a/src/esi/Context.h b/src/esi/Context.h index a3dae30b2c..4326dbe8a8 100644 --- a/src/esi/Context.h +++ b/src/esi/Context.h @@ -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" diff --git a/src/format/Format.cc b/src/format/Format.cc index a49f0e8665..f5bd47b1ae 100644 --- a/src/format/Format.cc +++ b/src/format/Format.cc @@ -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: diff --git a/src/http.cc b/src/http.cc index 09f9759322..04ecd8848b 100644 --- a/src/http.cc +++ b/src/http.cc @@ -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); } diff --git a/src/http/Stream.cc b/src/http/Stream.cc index 0e786d483f..6aa04d41aa 100644 --- a/src/http/Stream.cc +++ b/src/http/Stream.cc @@ -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 <e) { 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); } } diff --git a/src/http/Stream.h b/src/http/Stream.h index 7c543e01eb..7de6c0a5c2 100644 --- a/src/http/Stream.h +++ b/src/http/Stream.h @@ -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(); diff --git a/src/log/access_log.cc b/src/log/access_log.cc index 38887dd10e..00b73467b5 100644 --- a/src/log/access_log.cc +++ b/src/log/access_log.cc @@ -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" diff --git a/src/log/forward.h b/src/log/forward.h index fc7eb02cee..733d9b27c6 100644 --- a/src/log/forward.h +++ b/src/log/forward.h @@ -14,5 +14,8 @@ class AccessLogEntry; typedef RefCount AccessLogEntryPointer; +class LogTags; +class LogTagsErrors; + #endif /* SQUID_FORMAT_FORWARD_H */ diff --git a/src/mk-string-arrays.awk b/src/mk-string-arrays.awk index 9aac7ce19f..98a6da75c6 100644 --- a/src/mk-string-arrays.awk +++ b/src/mk-string-arrays.awk @@ -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 index 0000000000..7bec4098d6 --- /dev/null +++ b/src/security/ErrorDetail.cc @@ -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 +#endif +#endif +#include + +namespace Security { + +// we use std::map to optimize search; TODO: Use std::unordered_map instead? +typedef std::map 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 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(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(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(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 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 index 0000000000..89e3be9947 --- /dev/null +++ b/src/security/ErrorDetail.h @@ -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 index 0000000000..cc648e4df4 --- /dev/null +++ b/src/security/Io.cc @@ -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 +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 +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(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 index 0000000000..3a14b6fa1a --- /dev/null +++ b/src/security/Io.h @@ -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 */ + diff --git a/src/security/KeyData.cc b/src/security/KeyData.cc index 95b82fd2d3..ac165adc1b 100644 --- a/src/security/KeyData.cc +++ b/src/security/KeyData.cc @@ -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; diff --git a/src/security/LockingPointer.h b/src/security/LockingPointer.h index e75acf7b36..998466b029 100644 --- a/src/security/LockingPointer.h +++ b/src/security/LockingPointer.h @@ -57,13 +57,19 @@ public: /// a helper label to simplify this objects API definitions below typedef Security::LockingPointer 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); } diff --git a/src/security/Makefile.am b/src/security/Makefile.am index f729ba159a..19340f4a0b 100644 --- a/src/security/Makefile.am +++ b/src/security/Makefile.am @@ -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 \ diff --git a/src/security/PeerConnector.cc b/src/security/PeerConnector.cc index c9510edd4a..83986912fb 100644 --- a/src/security/PeerConnector.cc +++ b/src/security/PeerConnector.cc @@ -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 ¶ms) 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_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(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); } diff --git a/src/security/PeerConnector.h b/src/security/PeerConnector.h index 2e5efceb19..f0c7be97d8 100644 --- a/src/security/PeerConnector.h +++ b/src/security/PeerConnector.h @@ -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); diff --git a/src/security/PeerOptions.cc b/src/security/PeerOptions.cc index b8da60d95d..a2e683b523 100644 --- a/src/security/PeerOptions.cc +++ b/src/security/PeerOptions.cc @@ -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); diff --git a/src/security/ServerOptions.cc b/src/security/ServerOptions.cc index 182745638d..f9da3ea4e9 100644 --- a/src/security/ServerOptions.cc +++ b/src/security/ServerOptions.cc @@ -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); diff --git a/src/security/Session.cc b/src/security/Session.cc index 3499e980b7..183acbae8a 100644 --- a/src/security/Session.cc +++ b/src/security/Session.cc @@ -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) { diff --git a/src/security/forward.h b/src/security/forward.h index 177328031d..fad247d5f0 100644 --- a/src/security/forward.h +++ b/src/security/forward.h @@ -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 #endif #include +#include #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 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 > CertPointer; #elif USE_GNUTLS typedef std::shared_ptr CertPointer; #else -typedef std::shared_ptr CertPointer; +typedef std::shared_ptr 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 PrivateKeyPointer; class ServerOptions; +class ErrorDetail; +typedef RefCount 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::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 */ diff --git a/src/servers/FtpServer.cc b/src/servers/FtpServer.cc index 4da3743446..549f1aac88 100644 --- a/src/servers/FtpServer.cc +++ b/src/servers/FtpServer.cc @@ -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 diff --git a/src/servers/Server.cc b/src/servers/Server.cc index 865f347dc1..8eb03ea3fe 100644 --- a/src/servers/Server.cc +++ b/src/servers/Server.cc @@ -12,9 +12,11 @@ #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; } diff --git a/src/servers/Server.h b/src/servers/Server.h index 3a233da6a1..6c139cc91a 100644 --- a/src/servers/Server.h +++ b/src/servers/Server.h @@ -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 }; diff --git a/src/ssl/ErrorDetail.cc b/src/ssl/ErrorDetail.cc index c85c46a083..1161cc162e 100644 --- a/src/ssl/ErrorDetail.cc +++ b/src/ssl/ErrorDetail.cc @@ -9,387 +9,11 @@ #include "squid.h" #include "errorpage.h" #include "fatal.h" -#include "html_quote.h" #include "ssl/ErrorDetail.h" +#include "ssl/ErrorDetailManager.h" -#include #include -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 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 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; -} - diff --git a/src/ssl/ErrorDetail.h b/src/ssl/ErrorDetail.h index 4fc51d187f..02579dfb35 100644 --- a/src/ssl/ErrorDetail.h +++ b/src/ssl/ErrorDetail.h @@ -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 diff --git a/src/ssl/ErrorDetailManager.h b/src/ssl/ErrorDetailManager.h index 435cfc6644..8bb6e5a7ea 100644 --- a/src/ssl/ErrorDetailManager.h +++ b/src/ssl/ErrorDetailManager.h @@ -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) diff --git a/src/ssl/PeekingPeerConnector.cc b/src/ssl/PeekingPeerConnector.cc index e48e39b35b..5ea4e4ba91 100644 --- a/src/ssl/PeekingPeerConnector.cc +++ b/src/ssl/PeekingPeerConnector.cc @@ -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(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 diff --git a/src/ssl/PeekingPeerConnector.h b/src/ssl/PeekingPeerConnector.h index 1987db8333..41cf0b8825 100644 --- a/src/ssl/PeekingPeerConnector.h +++ b/src/ssl/PeekingPeerConnector.h @@ -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 diff --git a/src/ssl/cert_validate_message.cc b/src/ssl/cert_validate_message.cc index 602d1b1058..4e4f2d1b62 100644 --- a/src/ssl/cert_validate_message.cc +++ b/src/ssl/cert_validate_message.cc @@ -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) { diff --git a/src/ssl/cert_validate_message.h b/src/ssl/cert_validate_message.h index 58c8f51bd8..25905a72fb 100644 --- a/src/ssl/cert_validate_message.h +++ b/src/ssl/cert_validate_message.h @@ -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 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 }; diff --git a/src/ssl/support.cc b/src/ssl/support.cc index b290a4f76d..413cb366f5 100644 --- a/src/ssl/support.cc +++ b/src/ssl/support.cc @@ -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 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 (ptr); + const auto errDetail = static_cast(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); diff --git a/src/ssl/support.h b/src/ssl/support.h index f70bcf2b67..30833874a3 100644 --- a/src/ssl/support.h +++ b/src/ssl/support.h @@ -37,15 +37,6 @@ \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 CertValidationResponsePointer; diff --git a/src/tests/stub_HttpRequest.cc b/src/tests/stub_HttpRequest.cc index 0e36a9ee30..829f436c26 100644 --- a/src/tests/stub_HttpRequest.cc +++ b/src/tests/stub_HttpRequest.cc @@ -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 index 0000000000..331289f084 --- /dev/null +++ b/src/tests/stub_liberror.cc @@ -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()) diff --git a/src/tests/stub_libhttp.cc b/src/tests/stub_libhttp.cc index b5475018a5..89bd9611b0 100644 --- a/src/tests/stub_libhttp.cc +++ b/src/tests/stub_libhttp.cc @@ -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 diff --git a/src/tests/stub_libsecurity.cc b/src/tests/stub_libsecurity.cc index 10793dbbe6..6a2e2091a1 100644 --- a/src/tests/stub_libsecurity.cc +++ b/src/tests/stub_libsecurity.cc @@ -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 diff --git a/src/tests/stub_libsslsquid.cc b/src/tests/stub_libsslsquid.cc index 27662d2c39..6b0336fa10 100644 --- a/src/tests/stub_libsslsquid.cc +++ b/src/tests/stub_libsslsquid.cc @@ -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