... 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
src/DiskIO/DiskThreads/Makefile
src/DiskIO/IpcIo/Makefile
src/DiskIO/Mmapped/Makefile
+ src/error/Makefile
src/esi/Makefile
src/eui/Makefile
src/format/Makefile
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
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
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"
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
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 ()
{
#
# 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
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
{
#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"
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_;
#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"
#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"
typedef CbcPointer<HappyConnOpener> HappyConnOpenerPointer;
class HappyConnOpenerAnswer;
-#if USE_OPENSSL
-namespace Ssl
-{
-class ErrorDetail;
-class CertValidationResponse;
-};
-#endif
-
/// Sets initial TOS value and Netfilter for the future outgoing connection.
/// Updates the given Connection object, not the future transport connection.
void GetMarkingsToServer(HttpRequest * request, Comm::Connection &conn);
#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"
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;
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?
// 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;
* 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
// 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;
}
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;
}
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
#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"
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();
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 */
#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",
#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
/// \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
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
dlink.cc \
dlink.h \
enums.h \
- err_detail_type.h \
- err_type.h \
errorpage.cc \
errorpage.h \
event.cc \
BUILT_SOURCES = \
cf_gen_defines.cci \
cf_parser.cci \
- err_detail_type.cc \
- err_type.cc \
globals.cc \
hier_code.cc \
icp_opcode.cc \
anyp/libanyp.la \
security/libsecurity.la \
$(SSL_LIBS) \
+ error/liberror.la \
ipc/libipc.la \
mgr/libmgr.la \
proxyp/libproxyp.la \
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)
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 \
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 \
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 \
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 \
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 \
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 \
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 \
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 \
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 \
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)
{
/// 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 &);
#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"
#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"
#define SQUID_ACL_GADGETS_H
#include "acl/forward.h"
-#include "err_type.h"
+#include "error/forward.h"
#include <sstream>
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;
}
#define SQUID_ACLSQUIDERROR_H
#include "acl/Strategy.h"
-#include "err_type.h"
+#include "error/forward.h"
class ACLSquidErrorStrategy : public ACLStrategy<err_type>
{
#include "cache_cf.h"
#include "ConfigParser.h"
#include "Debug.h"
-#include "err_type.h"
+#include "error/Error.h"
#include "fatal.h"
#include "wordlist.h"
#include "acl/Data.h"
#include "base/CbDataList.h"
-#include "err_type.h"
+#include "error/forward.h"
/// \ingroup ACLAPI
class ACLSquidErrorData : public ACLData<err_type>
#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"
if (!canStartBypass || isRetriable) {
if (!isRetriable) {
if (const TextException *te = dynamic_cast<const TextException *>(&e))
- detailError(ERR_DETAIL_EXCEPTION_START + te->id());
+ detailError(new ExceptionErrorDetail(te->id()));
else
- detailError(ERR_DETAIL_EXCEPTION_OTHER);
+ detailError(new ExceptionErrorDetail(Here().id()));
}
Adaptation::Icap::Xaction::callException(e);
return;
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);
}
}
// 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");
}
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();
return true;
}
-void Adaptation::Icap::ModXact::detailError(int errDetail)
+void Adaptation::Icap::ModXact::detailError(const ErrorDetail::Pointer &errDetail)
{
HttpRequest *request = dynamic_cast<HttpRequest*>(adapted.header);
// if no adapted request, update virgin (and inherit its properties later)
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
#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"
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");
}
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");
}
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");
}
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");
}
#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"
void handleSecuredPeer(Security::EncryptorAnswer &answer);
/// record error detail if possible
- virtual void detailError(int) {}
+ virtual void detailError(const ErrorDetailPointer &) {}
void openConnection();
void closeConnection();
return result.what();
}
+std::ostream &
+operator <<(std::ostream &os, const TextException &ex)
+{
+ ex.print(os);
+ return os;
+}
+
std::ostream &
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();
}
/// 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())
#include "comm/TcpAcceptor.h"
#include "comm/Write.h"
#include "CommCalls.h"
+#include "error/ExceptionErrorDetail.h"
#include "errorpage.h"
#include "fd.h"
#include "fde.h"
#include "profiler/Profiler.h"
#include "proxyp/Header.h"
#include "proxyp/Parser.h"
+#include "sbuf/Stream.h"
+#include "security/Io.h"
#include "security/NegotiationHistory.h"
#include "servers/forward.h"
#include "SquidConfig.h"
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
{
clientUpdateStatCounters(logType);
- if (request->errType != ERR_NONE)
+ if (request->error)
++ statCounter.client_http.errors;
clientUpdateStatHistCounters(logType,
// 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;
}
}
}
#endif
+void
+ConnStateData::resetReadTimeout(const time_t timeout)
+{
+ typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
+ AsyncCall::Pointer callback = JobCallback(33, 5, TimeoutDialer, this, ConnStateData::requestTimeout);
+ commSetConnTimeout(clientConnection, timeout, callback);
+}
+
+void
+ConnStateData::extendLifetime()
+{
+ typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
+ AsyncCall::Pointer callback = JobCallback(5, 4, TimeoutDialer, this, ConnStateData::lifetimeTimeout);
+ commSetConnTimeout(clientConnection, Config.Timeout.lifetime, callback);
+}
+
// cleans up before destructor is called
void
ConnStateData::swanSong()
{
debugs(33, 2, HERE << clientConnection);
- checkLogging();
flags.readMore = false;
clientdbEstablished(clientConnection->remote, -1); /* decrement */
- pipeline.terminateAll(0);
+
+ terminateAll(ERR_NONE, LogTagsErrors());
+ checkLogging();
// XXX: Closing pinned conn is too harsh: The Client may want to continue!
unpinConnection(true);
- Server::swanSong(); // closes the client connection
+ Server::swanSong();
#if USE_AUTH
// NP: do this bit after closing the connections to avoid side effects from unwanted TCP RST
flags.swanSang = true;
}
+void
+ConnStateData::callException(const std::exception &ex)
+{
+ Server::callException(ex); // logs ex and stops the job
+
+ ErrorDetail::Pointer errorDetail;
+ if (const auto tex = dynamic_cast<const TextException*>(&ex))
+ errorDetail = new ExceptionErrorDetail(tex->id());
+ else
+ errorDetail = new ExceptionErrorDetail(Here().id());
+ updateError(ERR_GATEWAY_FAILURE, errorDetail);
+}
+
+void
+ConnStateData::updateError(const Error &error)
+{
+ if (const auto context = pipeline.front()) {
+ const auto http = context->http;
+ assert(http);
+ http->updateError(error);
+ } else {
+ bareError.update(error);
+ }
+}
+
bool
ConnStateData::isOpen() const
{
/**
* Set the timeout BEFORE calling readSomeData().
*/
- typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
- AsyncCall::Pointer timeoutCall = JobCallback(33, 5,
- TimeoutDialer, this, ConnStateData::requestTimeout);
- commSetConnTimeout(clientConnection, clientConnection->timeLeft(idleTimeout()), timeoutCall);
+ resetReadTimeout(clientConnection->timeLeft(idleTimeout()));
readSomeData();
/** Please don't do anything with the FD past here! */
}
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;
}
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;
}
// 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();
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
{
return;
receivedFirstByte_ = true;
- // Set timeout to Config.Timeout.request
- typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
- AsyncCall::Pointer timeoutCall = JobCallback(33, 5,
- TimeoutDialer, this, ConnStateData::requestTimeout);
- commSetConnTimeout(clientConnection, Config.Timeout.request, timeoutCall);
+ resetReadTimeout(Config.Timeout.request);
}
/**
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
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();
return;
const err_type error = receivedFirstByte_ ? ERR_REQUEST_PARSE_TIMEOUT : ERR_REQUEST_START_TIMEOUT;
+ updateError(error);
if (tunnelOnError(HttpRequestMethod(), error))
return;
io.conn->close();
}
-static void
-clientLifetimeTimeout(const CommTimeoutCbParams &io)
+void
+ConnStateData::lifetimeTimeout(const CommTimeoutCbParams &io)
{
- ClientHttpRequest *http = static_cast<ClientHttpRequest *>(io.data);
- debugs(33, DBG_IMPORTANT, "WARNING: Closing client connection due to lifetime timeout");
- debugs(33, DBG_IMPORTANT, "\t" << http->uri);
- if (const auto conn = http->getConn())
- conn->pipeline.terminateAll(ETIMEDOUT);
- if (Comm::IsConnOpen(io.conn))
- io.conn->close();
+ debugs(33, DBG_IMPORTANT, "WARNING: Closing client connection due to lifetime timeout" <<
+ Debug::Extra << "connection: " << io.conn);
+
+ LogTagsErrors lte;
+ lte.timedout = true;
+ terminateAll(ERR_LIFETIME_EXP, lte);
}
ConnStateData::ConnStateData(const MasterXaction::Pointer &xact) :
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;
}
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();
}
if (!ctx || !httpsCreate(connState, ctx))
return;
- typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
- AsyncCall::Pointer timeoutCall = JobCallback(33, 5, TimeoutDialer,
- connState, ConnStateData::requestTimeout);
- commSetConnTimeout(details, Config.Timeout.request, timeoutCall);
+ connState->resetReadTimeout(Config.Timeout.request);
Comm::SetSelect(details->fd, COMM_SELECT_READ, clientNegotiateSSL, connState, 0);
}
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);
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);
// bumped intercepted conns should already have Config.Timeout.request set
// but forwarded connections may only have Config.Timeout.lifetime. [Re]set
// to make sure the connection does not get stuck on non-SSL clients.
- typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
- AsyncCall::Pointer timeoutCall = JobCallback(33, 5, TimeoutDialer,
- this, ConnStateData::requestTimeout);
- commSetConnTimeout(clientConnection, Config.Timeout.request, timeoutCall);
+ resetReadTimeout(Config.Timeout.request);
switchedToHttps_ = true;
// commSetConnTimeout() was called for this request before we switched.
// Fix timeout to request_start_timeout
- typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
- AsyncCall::Pointer timeoutCall = JobCallback(33, 5,
- TimeoutDialer, this, ConnStateData::requestTimeout);
- commSetConnTimeout(clientConnection, Config.Timeout.request_start_timeout, timeoutCall);
+ resetReadTimeout(Config.Timeout.request_start_timeout);
// Also reset receivedFirstByte_ flag to allow this timeout work in the case we have
// a bumbed "connect" request on non transparent port.
receivedFirstByte_ = false;
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
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;
// 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;
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
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()
{
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;
}
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);
}
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
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
#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"
#include "auth/UserRequest.h"
#endif
#if USE_OPENSSL
+#include "security/forward.h"
#include "security/Handshake.h"
#include "ssl/support.h"
#endif
/// 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 */
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.
#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;
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);
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;
/// 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
#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"
maxReplyBodySize_(0),
entry_(NULL),
loggingEntry_(NULL),
- conn_(NULL)
+ conn_(cbdataReference(aConn))
#if USE_OPENSSL
, sslBumpNeed_(Ssl::bumpEnd)
#endif
, request_satisfaction_offset(0)
#endif
{
- setConn(aConn);
al = new AccessLogEntry;
CodeContext::Reset(al);
al->cache.start_time = current_time;
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()) {
loggingEntry(NULL);
if (request)
- checkFailureRatio(request->errType, al->hier.code);
+ checkFailureRatio(request->error.category, al->hier.code);
freeResources();
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;
#endif
+void
+ClientHttpRequest::updateError(const Error &error)
+{
+ if (request)
+ request->error.update(error);
+ else
+ al->updateError(error);
+}
+
bool
ClientHttpRequest::gotEnough() const
{
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
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);
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 << ")");
// 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
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.
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
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);
#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"
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
}
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;
}
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);
#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"
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
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()
}
#define SQUID_FTP_CLIENT_H
#include "clients/Client.h"
+#include "error/Detail.h"
class String;
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
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;
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());
#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"
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
} 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));
params.flag = Comm::INPROGRESS;
else
params.flag = Comm::COMM_ERROR;
+ params.size = 0;
}
return params.flag;
+++ /dev/null
-/*
- * 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
-
--- /dev/null
+/*
+ * 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);
+}
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+/*
+ * 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;
+}
+
--- /dev/null
+/*
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#ifndef _SQUID_SRC_ERROR_ERROR_H
+#define _SQUID_SRC_ERROR_ERROR_H
+
+#include "error/forward.h"
+#include "error/Detail.h"
+
+#include <iosfwd>
+
+/// a transaction problem
+class Error {
+public:
+ Error() = default;
+ Error(const err_type c): category(c) {} ///< support implicit conversions
+ Error(const err_type c, const ErrorDetailPointer &d): category(c), detail(d) {}
+
+ explicit operator bool() const { return category != ERR_NONE; }
+
+ /// if necessary, stores the given error information (if any)
+ void update(const Error &);
+
+ /// convenience wrapper for update(Error)
+ void update(const err_type c, const ErrorDetailPointer &d) { update(Error(c, d)); }
+
+ /// switch to the default "no error information" state
+ void clear() { *this = Error(); }
+
+ err_type category = ERR_NONE; ///< primary error classification (or ERR_NONE)
+ ErrorDetailPointer detail; ///< additional details about the error
+};
+
+extern const char *err_type_str[];
+
+inline
+err_type
+errorTypeByName(const char *name)
+{
+ for (int i = 0; i < ERR_MAX; ++i)
+ if (strcmp(name, err_type_str[i]) == 0)
+ return (err_type)i;
+ return ERR_MAX;
+}
+
+inline
+const char *
+errorTypeName(err_type err)
+{
+ if (err < ERR_NONE || err >= ERR_MAX)
+ return "UNKNOWN";
+ return err_type_str[err];
+}
+
+std::ostream &operator <<(std::ostream &os, const Error &error);
+
+#endif /* _SQUID_SRC_ERROR_ERROR_H */
+
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+## 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)
+
--- /dev/null
+/*
+ * 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 */
+
* 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,
// 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
ERR_MAX
} err_type;
-extern const char *err_type_str[];
-
-inline
-err_type
-errorTypeByName(const char *name)
-{
- for (int i = 0; i < ERR_MAX; ++i)
- if (strcmp(name, err_type_str[i]) == 0)
- return (err_type)i;
- return ERR_MAX;
-}
-
-inline
-const char *
-errorTypeName(err_type err)
-{
- if (err < ERR_NONE || err >= ERR_MAX)
- return "UNKNOWN";
- return err_type_str[err];
-}
-
-#endif /* _SQUID_ERR_TYPE_H */
+class Error;
+class ErrorDetail;
+
+typedef RefCount<ErrorDetail> ErrorDetailPointer;
+
+#endif /* _SQUID_SRC_ERROR_FORWARD_H */
#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"
TCP_RESET,
"reset"
},
+ {
+ ERR_CLIENT_GONE,
+ "unexpected client disconnect"
+ },
{
ERR_SECURE_ACCEPT_FAIL,
"secure accept fail"
if (err_language != Config.errorDefaultLanguage)
#endif
safe_free(err_language);
-#if USE_OPENSSL
- delete detail;
-#endif
}
int
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;
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':
// 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;
#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"
#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);
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();
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_;
#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"
#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"
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:
#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"
// 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);
}
assert(!connRegistered_);
assert(getConn());
connRegistered_ = true;
- getConn()->pipeline.add(Http::StreamPointer(this));
+ getConn()->add(this);
}
bool
/// 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);
}
}
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();
#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"
class AccessLogEntry;
typedef RefCount<AccessLogEntry> AccessLogEntryPointer;
+class LogTags;
+class LogTagsErrors;
+
#endif /* SQUID_FORMAT_FORWARD_H */
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
--- /dev/null
+/*
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#include "squid.h"
+#include "error/SysErrorDetail.h"
+#include "html_quote.h"
+#include "sbuf/SBuf.h"
+#include "sbuf/Stream.h"
+#include "security/ErrorDetail.h"
+#include "security/forward.h"
+#include "security/Io.h"
+#include "util.h"
+
+#if USE_OPENSSL
+#include "ssl/ErrorDetailManager.h"
+#elif USE_GNUTLS
+#if HAVE_GNUTLS_GNUTLS_H
+#include <gnutls/gnutls.h>
+#endif
+#endif
+#include <map>
+
+namespace Security {
+
+// we use std::map to optimize search; TODO: Use std::unordered_map instead?
+typedef std::map<ErrorCode, const char *> ErrorCodeNames;
+static const ErrorCodeNames TheErrorCodeNames = {
+ { SQUID_TLS_ERR_ACCEPT,
+ "SQUID_TLS_ERR_ACCEPT"
+ },
+ { SQUID_TLS_ERR_CONNECT,
+ "SQUID_TLS_ERR_CONNECT"
+ },
+ { SQUID_X509_V_ERR_INFINITE_VALIDATION,
+ "SQUID_X509_V_ERR_INFINITE_VALIDATION"
+ },
+ { SQUID_X509_V_ERR_CERT_CHANGE,
+ "SQUID_X509_V_ERR_CERT_CHANGE"
+ },
+ { SQUID_X509_V_ERR_DOMAIN_MISMATCH,
+ "SQUID_X509_V_ERR_DOMAIN_MISMATCH"
+ },
+#if USE_OPENSSL
+ { X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT,
+ "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT"
+ },
+ { X509_V_ERR_UNABLE_TO_GET_CRL,
+ "X509_V_ERR_UNABLE_TO_GET_CRL"
+ },
+ { X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE,
+ "X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE"
+ },
+ { X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE,
+ "X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE"
+ },
+ { X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY,
+ "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY"
+ },
+ { X509_V_ERR_CERT_SIGNATURE_FAILURE,
+ "X509_V_ERR_CERT_SIGNATURE_FAILURE"
+ },
+ { X509_V_ERR_CRL_SIGNATURE_FAILURE,
+ "X509_V_ERR_CRL_SIGNATURE_FAILURE"
+ },
+ { X509_V_ERR_CERT_NOT_YET_VALID,
+ "X509_V_ERR_CERT_NOT_YET_VALID"
+ },
+ { X509_V_ERR_CERT_HAS_EXPIRED,
+ "X509_V_ERR_CERT_HAS_EXPIRED"
+ },
+ { X509_V_ERR_CRL_NOT_YET_VALID,
+ "X509_V_ERR_CRL_NOT_YET_VALID"
+ },
+ { X509_V_ERR_CRL_HAS_EXPIRED,
+ "X509_V_ERR_CRL_HAS_EXPIRED"
+ },
+ { X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD,
+ "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD"
+ },
+ { X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD,
+ "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD"
+ },
+ { X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD,
+ "X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD"
+ },
+ { X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD,
+ "X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD"
+ },
+ { X509_V_ERR_OUT_OF_MEM,
+ "X509_V_ERR_OUT_OF_MEM"
+ },
+ { X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,
+ "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
+ },
+ { X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN,
+ "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN"
+ },
+ { X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
+ "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
+ },
+ { X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE,
+ "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
+ },
+ { X509_V_ERR_CERT_CHAIN_TOO_LONG,
+ "X509_V_ERR_CERT_CHAIN_TOO_LONG"
+ },
+ { X509_V_ERR_CERT_REVOKED,
+ "X509_V_ERR_CERT_REVOKED"
+ },
+ { X509_V_ERR_INVALID_CA,
+ "X509_V_ERR_INVALID_CA"
+ },
+ { X509_V_ERR_PATH_LENGTH_EXCEEDED,
+ "X509_V_ERR_PATH_LENGTH_EXCEEDED"
+ },
+ { X509_V_ERR_INVALID_PURPOSE,
+ "X509_V_ERR_INVALID_PURPOSE"
+ },
+ { X509_V_ERR_CERT_UNTRUSTED,
+ "X509_V_ERR_CERT_UNTRUSTED"
+ },
+ { X509_V_ERR_CERT_REJECTED,
+ "X509_V_ERR_CERT_REJECTED"
+ },
+ { X509_V_ERR_SUBJECT_ISSUER_MISMATCH,
+ "X509_V_ERR_SUBJECT_ISSUER_MISMATCH"
+ },
+ { X509_V_ERR_AKID_SKID_MISMATCH,
+ "X509_V_ERR_AKID_SKID_MISMATCH"
+ },
+ { X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH,
+ "X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH"
+ },
+ { X509_V_ERR_KEYUSAGE_NO_CERTSIGN,
+ "X509_V_ERR_KEYUSAGE_NO_CERTSIGN"
+ },
+#if defined(X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER)
+ {
+ X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER, // 33
+ "X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER"
+ },
+#endif
+#if defined(X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION)
+ {
+ X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION, // 34
+ "X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION"
+ },
+#endif
+#if defined(X509_V_ERR_KEYUSAGE_NO_CRL_SIGN)
+ {
+ X509_V_ERR_KEYUSAGE_NO_CRL_SIGN, // 35
+ "X509_V_ERR_KEYUSAGE_NO_CRL_SIGN"
+ },
+#endif
+#if defined(X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION)
+ {
+ X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION, // 36
+ "X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION"
+ },
+#endif
+#if defined(X509_V_ERR_INVALID_NON_CA)
+ {
+ X509_V_ERR_INVALID_NON_CA, // 37
+ "X509_V_ERR_INVALID_NON_CA"
+ },
+#endif
+#if defined(X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED)
+ {
+ X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED, // 38
+ "X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED"
+ },
+#endif
+#if defined(X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE)
+ {
+ X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE, // 39
+ "X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE"
+ },
+#endif
+#if defined(X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED)
+ {
+ X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED, // 40
+ "X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED"
+ },
+#endif
+#if defined(X509_V_ERR_INVALID_EXTENSION)
+ {
+ X509_V_ERR_INVALID_EXTENSION, // 41
+ "X509_V_ERR_INVALID_EXTENSION"
+ },
+#endif
+#if defined(X509_V_ERR_INVALID_POLICY_EXTENSION)
+ {
+ X509_V_ERR_INVALID_POLICY_EXTENSION, // 42
+ "X509_V_ERR_INVALID_POLICY_EXTENSION"
+ },
+#endif
+#if defined(X509_V_ERR_NO_EXPLICIT_POLICY)
+ {
+ X509_V_ERR_NO_EXPLICIT_POLICY, // 43
+ "X509_V_ERR_NO_EXPLICIT_POLICY"
+ },
+#endif
+#if defined(X509_V_ERR_DIFFERENT_CRL_SCOPE)
+ {
+ X509_V_ERR_DIFFERENT_CRL_SCOPE, // 44
+ "X509_V_ERR_DIFFERENT_CRL_SCOPE"
+ },
+#endif
+#if defined(X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE)
+ {
+ X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE, // 45
+ "X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE"
+ },
+#endif
+#if defined(X509_V_ERR_UNNESTED_RESOURCE)
+ {
+ X509_V_ERR_UNNESTED_RESOURCE, // 46
+ "X509_V_ERR_UNNESTED_RESOURCE"
+ },
+#endif
+#if defined(X509_V_ERR_PERMITTED_VIOLATION)
+ {
+ X509_V_ERR_PERMITTED_VIOLATION, // 47
+ "X509_V_ERR_PERMITTED_VIOLATION"
+ },
+#endif
+#if defined(X509_V_ERR_EXCLUDED_VIOLATION)
+ {
+ X509_V_ERR_EXCLUDED_VIOLATION, // 48
+ "X509_V_ERR_EXCLUDED_VIOLATION"
+ },
+#endif
+#if defined(X509_V_ERR_SUBTREE_MINMAX)
+ {
+ X509_V_ERR_SUBTREE_MINMAX, // 49
+ "X509_V_ERR_SUBTREE_MINMAX"
+ },
+#endif
+ { X509_V_ERR_APPLICATION_VERIFICATION, // 50
+ "X509_V_ERR_APPLICATION_VERIFICATION"
+ },
+#if defined(X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE)
+ {
+ X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE, // 51
+ "X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE"
+ },
+#endif
+#if defined(X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX)
+ {
+ X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX, // 52
+ "X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX"
+ },
+#endif
+#if defined(X509_V_ERR_UNSUPPORTED_NAME_SYNTAX)
+ {
+ X509_V_ERR_UNSUPPORTED_NAME_SYNTAX, // 53
+ "X509_V_ERR_UNSUPPORTED_NAME_SYNTAX"
+ },
+#endif
+#if defined(X509_V_ERR_CRL_PATH_VALIDATION_ERROR)
+ {
+ X509_V_ERR_CRL_PATH_VALIDATION_ERROR, // 54
+ "X509_V_ERR_CRL_PATH_VALIDATION_ERROR"
+ },
+#endif
+#if defined(X509_V_ERR_PATH_LOOP)
+ {
+ X509_V_ERR_PATH_LOOP, // 55
+ "X509_V_ERR_PATH_LOOP"
+ },
+#endif
+#if defined(X509_V_ERR_SUITE_B_INVALID_VERSION)
+ {
+ X509_V_ERR_SUITE_B_INVALID_VERSION, // 56
+ "X509_V_ERR_SUITE_B_INVALID_VERSION"
+ },
+#endif
+#if defined(X509_V_ERR_SUITE_B_INVALID_ALGORITHM)
+ {
+ X509_V_ERR_SUITE_B_INVALID_ALGORITHM, // 57
+ "X509_V_ERR_SUITE_B_INVALID_ALGORITHM"
+ },
+#endif
+#if defined(X509_V_ERR_SUITE_B_INVALID_CURVE)
+ {
+ X509_V_ERR_SUITE_B_INVALID_CURVE, // 58
+ "X509_V_ERR_SUITE_B_INVALID_CURVE"
+ },
+#endif
+#if defined(X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM)
+ {
+ X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM, // 59
+ "X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM"
+ },
+#endif
+#if defined(X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED)
+ {
+ X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED, // 60
+ "X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED"
+ },
+#endif
+#if defined(X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256)
+ {
+ X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256, // 61
+ "X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256"
+ },
+#endif
+#if defined(X509_V_ERR_HOSTNAME_MISMATCH)
+ {
+ X509_V_ERR_HOSTNAME_MISMATCH, // 62
+ "X509_V_ERR_HOSTNAME_MISMATCH"
+ },
+#endif
+#if defined(X509_V_ERR_EMAIL_MISMATCH)
+ {
+ X509_V_ERR_EMAIL_MISMATCH, // 63
+ "X509_V_ERR_EMAIL_MISMATCH"
+ },
+#endif
+#if defined(X509_V_ERR_IP_ADDRESS_MISMATCH)
+ {
+ X509_V_ERR_IP_ADDRESS_MISMATCH, // 64
+ "X509_V_ERR_IP_ADDRESS_MISMATCH"
+ },
+#endif
+#if defined(X509_V_ERR_DANE_NO_MATCH)
+ {
+ X509_V_ERR_DANE_NO_MATCH, // 65
+ "X509_V_ERR_DANE_NO_MATCH"
+ },
+#endif
+#if defined(X509_V_ERR_EE_KEY_TOO_SMALL)
+ {
+ X509_V_ERR_EE_KEY_TOO_SMALL, // 66
+ "X509_V_ERR_EE_KEY_TOO_SMALL"
+ },
+#endif
+#if defined(X509_V_ERR_CA_KEY_TOO_SMALL)
+ {
+ X509_V_ERR_CA_KEY_TOO_SMALL, // 67
+ "X509_V_ERR_CA_KEY_TOO_SMALL"
+ },
+#endif
+#if defined(X509_V_ERR_CA_MD_TOO_WEAK)
+ {
+ X509_V_ERR_CA_MD_TOO_WEAK, // 68
+ "X509_V_ERR_CA_MD_TOO_WEAK"
+ },
+#endif
+#if defined(X509_V_ERR_INVALID_CALL)
+ {
+ X509_V_ERR_INVALID_CALL, // 69
+ "X509_V_ERR_INVALID_CALL"
+ },
+#endif
+#if defined(X509_V_ERR_STORE_LOOKUP)
+ {
+ X509_V_ERR_STORE_LOOKUP, // 70
+ "X509_V_ERR_STORE_LOOKUP"
+ },
+#endif
+#if defined(X509_V_ERR_NO_VALID_SCTS)
+ {
+ X509_V_ERR_NO_VALID_SCTS, // 71
+ "X509_V_ERR_NO_VALID_SCTS"
+ },
+#endif
+#if defined(X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION)
+ {
+ X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION, // 72
+ "X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION"
+ },
+#endif
+#if defined(X509_V_ERR_OCSP_VERIFY_NEEDED)
+ {
+ X509_V_ERR_OCSP_VERIFY_NEEDED, // 73
+ "X509_V_ERR_OCSP_VERIFY_NEEDED"
+ },
+#endif
+#if defined(X509_V_ERR_OCSP_VERIFY_FAILED)
+ {
+ X509_V_ERR_OCSP_VERIFY_FAILED, // 74
+ "X509_V_ERR_OCSP_VERIFY_FAILED"
+ },
+#endif
+#if defined(X509_V_ERR_OCSP_CERT_UNKNOWN)
+ {
+ X509_V_ERR_OCSP_CERT_UNKNOWN, // 75
+ "X509_V_ERR_OCSP_CERT_UNKNOWN"
+ },
+#endif
+ {
+ SSL_ERROR_NONE,
+ "SSL_ERROR_NONE"
+ },
+#endif // USE_OPENSSL
+};
+
+} // namespace Security
+
+Security::ErrorCode
+Security::ErrorCodeFromName(const char *name)
+{
+ static auto TheCmp = [](const char *a, const char *b){return strcmp(a, b) < 0;};
+ static std::map<const char *, ErrorCode, decltype(TheCmp)> TheErrorCodeByNameIndx(TheCmp);
+ if (TheErrorCodeByNameIndx.empty()) {
+ for (const auto &i: TheErrorCodeNames)
+ TheErrorCodeByNameIndx.insert(std::make_pair(i.second, i.first));
+
+ // redirector to support legacy error translations
+ TheErrorCodeByNameIndx.insert(std::make_pair("SQUID_ERR_SSL_HANDSHAKE", SQUID_TLS_ERR_CONNECT));
+ }
+
+ const auto it = TheErrorCodeByNameIndx.find(name);
+ if (it != TheErrorCodeByNameIndx.end())
+ return it->second;
+
+ return 0;
+}
+
+const char *
+Security::ErrorNameFromCode(const ErrorCode err, const bool prefixRawCode)
+{
+ const auto it = TheErrorCodeNames.find(err);
+ if (it != TheErrorCodeNames.end())
+ return it->second;
+
+ static char tmpBuffer[128];
+ snprintf(tmpBuffer, sizeof(tmpBuffer), "%s%d",
+ (prefixRawCode ? "SSL_ERR=" : ""), static_cast<int>(err));
+ return tmpBuffer;
+}
+
+/* Security::ErrorDetail */
+
+uint64_t Security::ErrorDetail::Generations = 0;
+
+/// helper constructor implementing the logic shared by the two public ones
+Security::ErrorDetail::ErrorDetail(const ErrorCode err, const int aSysErrorNo):
+ generation(++Generations),
+ error_no(err),
+ // We could restrict errno(3) collection to cases where the TLS library
+ // explicitly talks about the errno being set, but correctly detecting those
+ // cases is difficult. We simplify in hope that all other cases will either
+ // have a useful errno or a zero errno.
+ sysErrorNo(aSysErrorNo)
+{
+#if USE_OPENSSL
+ /// Extract and remember errors stored internally by the TLS library.
+ if ((lib_error_no = ERR_get_error())) {
+ debugs(83, 7, "got " << asHex(lib_error_no));
+ // more errors may be stacked
+ // TODO: Save/detail all stacked errors by always flushing stale ones.
+ ForgetErrors();
+ }
+#else
+ // other libraries return errors explicitly instead of auto-storing them
+#endif
+}
+
+Security::ErrorDetail::ErrorDetail(const ErrorCode anErrorCode, const CertPointer &cert, const CertPointer &broken, const char *aReason):
+ ErrorDetail(anErrorCode, 0)
+{
+ errReason = aReason;
+ peer_cert = cert;
+ broken_cert = broken ? broken : cert;
+}
+
+#if USE_OPENSSL
+Security::ErrorDetail::ErrorDetail(const ErrorCode anErrorCode, const int anIoErrorNo, const int aSysErrorNo):
+ ErrorDetail(anErrorCode, aSysErrorNo)
+{
+ ioErrorNo = anIoErrorNo;
+}
+
+#elif USE_GNUTLS
+Security::ErrorDetail::ErrorDetail(const ErrorCode anErrorCode, const LibErrorCode aLibErrorNo, const int aSysErrorNo):
+ ErrorDetail(anErrorCode, aSysErrorNo)
+{
+ lib_error_no = aLibErrorNo;
+}
+#endif
+
+void
+Security::ErrorDetail::setPeerCertificate(const CertPointer &cert)
+{
+ assert(cert);
+ assert(!peer_cert);
+ assert(!broken_cert);
+ peer_cert = cert;
+ // unlike the constructor, the supplied certificate is not a broken_cert
+}
+
+SBuf
+Security::ErrorDetail::brief() const
+{
+ SBuf buf(err_code()); // TODO: Upgrade err_code()/etc. to return SBuf.
+
+ if (lib_error_no) {
+#if USE_OPENSSL
+ // TODO: Log ERR_error_string_n() instead, despite length, whitespace?
+ // Example: `error:1408F09C:SSL routines:ssl3_get_record:http request`.
+ buf.append(ToSBuf("+TLS_LIB_ERR=", std::hex, std::uppercase, lib_error_no));
+#elif USE_GNUTLS
+ buf.append(ToSBuf("+", gnutls_strerror_name(lib_error_no)));
+#endif
+ }
+
+#if USE_OPENSSL
+ // TODO: Consider logging long but human-friendly names (e.g.,
+ // SSL_ERROR_SYSCALL).
+ if (ioErrorNo)
+ buf.append(ToSBuf("+TLS_IO_ERR=", ioErrorNo));
+#endif
+
+ if (sysErrorNo) {
+ buf.append('+');
+ buf.append(SysErrorDetail::Brief(sysErrorNo));
+ }
+
+ if (broken_cert)
+ buf.append("+broken_cert");
+
+ return buf;
+}
+
+SBuf
+Security::ErrorDetail::verbose(const HttpRequestPointer &request) const
+{
+ char const *format = nullptr;
+#if USE_OPENSSL
+ if (Ssl::ErrorDetailsManager::GetInstance().getErrorDetail(error_no, request, detailEntry))
+ format = detailEntry.detail.termedBuf();
+#endif
+ if (!format)
+ format = "SSL handshake error (%err_name)";
+
+ SBuf errDetailStr;
+ assert(format);
+ auto remainder = format;
+ while (auto p = strchr(remainder, '%')) {
+ errDetailStr.append(remainder, p - remainder);
+ char const *converted = nullptr;
+ const auto formattingCodeLen = convert(++p, &converted);
+ if (formattingCodeLen)
+ errDetailStr.append(converted);
+ else
+ errDetailStr.append("%");
+ remainder = p + formattingCodeLen;
+ }
+ errDetailStr.append(remainder, strlen(remainder));
+ return errDetailStr;
+}
+
+/// textual representation of the subject of the broken certificate
+const char *
+Security::ErrorDetail::subject() const
+{
+#if USE_OPENSSL
+ if (broken_cert.get()) {
+ static char tmpBuffer[256]; // A temporary buffer
+ if (X509_NAME_oneline(X509_get_subject_name(broken_cert.get()), tmpBuffer, sizeof(tmpBuffer))) {
+ // quote to avoid possible html code injection through
+ // certificate subject
+ return html_quote(tmpBuffer);
+ }
+ }
+#endif // USE_OPENSSL
+ return "[Not available]";
+}
+
+#if USE_OPENSSL
+/// helper function to collect CNs using Ssl::matchX509CommonNames()
+static int
+copy_cn(void *check_data, ASN1_STRING *cn_data)
+{
+ const auto str = static_cast<String*>(check_data);
+ if (!str) // no data? abort
+ return 0;
+ if (cn_data && cn_data->length) {
+ if (str->size() > 0)
+ str->append(", ");
+ str->append(reinterpret_cast<const char *>(cn_data->data), cn_data->length);
+ }
+ return 1;
+}
+#endif // USE_OPENSSL
+
+/// a list of the broken certificates CN and alternate names
+const char *
+Security::ErrorDetail::cn() const
+{
+#if USE_OPENSSL
+ if (broken_cert.get()) {
+ static String tmpStr;
+ tmpStr.clean();
+ Ssl::matchX509CommonNames(broken_cert.get(), &tmpStr, copy_cn);
+ if (tmpStr.size()) {
+ // quote to avoid possible HTML code injection through
+ // certificate subject
+ return html_quote(tmpStr.termedBuf());
+ }
+ }
+#endif // USE_OPENSSL
+ return "[Not available]";
+}
+
+/// the issuer of the broken certificate
+const char *
+Security::ErrorDetail::ca_name() const
+{
+#if USE_OPENSSL
+ if (broken_cert.get()) {
+ static char tmpBuffer[256]; // A temporary buffer
+ if (X509_NAME_oneline(X509_get_issuer_name(broken_cert.get()), tmpBuffer, sizeof(tmpBuffer))) {
+ // quote to avoid possible html code injection through
+ // certificate issuer subject
+ return html_quote(tmpBuffer);
+ }
+ }
+#endif // USE_OPENSSL
+ return "[Not available]";
+}
+
+/// textual representation of the "not before" field of the broken certificate
+const char *
+Security::ErrorDetail::notbefore() const
+{
+#if USE_OPENSSL
+ if (broken_cert.get()) {
+ if (const auto tm = X509_getm_notBefore(broken_cert.get())) {
+ static char tmpBuffer[256]; // A temporary buffer
+ Ssl::asn1timeToString(tm, tmpBuffer, sizeof(tmpBuffer));
+ return tmpBuffer;
+ }
+ }
+#endif // USE_OPENSSL
+ return "[Not available]";
+}
+
+/// textual representation of the "not after" field of the broken certificate
+const char *
+Security::ErrorDetail::notafter() const
+{
+#if USE_OPENSSL
+ if (broken_cert.get()) {
+ if (const auto tm = X509_getm_notAfter(broken_cert.get())) {
+ static char tmpBuffer[256]; // A temporary buffer
+ Ssl::asn1timeToString(tm, tmpBuffer, sizeof(tmpBuffer));
+ return tmpBuffer;
+ }
+ }
+#endif // USE_OPENSSL
+ return "[Not available]";
+}
+
+/// textual representation of error_no
+const char *
+Security::ErrorDetail::err_code() const
+{
+#if USE_OPENSSL
+ // try detailEntry first because it is faster
+ if (const char *err = detailEntry.name.termedBuf())
+ return err;
+#endif
+
+ return ErrorNameFromCode(error_no);
+}
+
+/// short description of error_no
+const char *
+Security::ErrorDetail::err_descr() const
+{
+ if (!error_no)
+ return "[No Error]";
+#if USE_OPENSSL
+ if (const char *err = detailEntry.descr.termedBuf())
+ return err;
+#endif
+ return "[Not available]";
+}
+
+/// textual representation of lib_error_no
+const char *
+Security::ErrorDetail::err_lib_error() const
+{
+ if (errReason.size() > 0)
+ return errReason.termedBuf();
+ else if (lib_error_no)
+ return ErrorString(lib_error_no);
+ else
+ return "[No Error]";
+ return "[Not available]";
+}
+
+/**
+ * Converts the code to a string value. Supported formatting codes are:
+ *
+ * Error meta information:
+ * %err_name: The name of a high-level SSL error (e.g., X509_V_ERR_*)
+ * %ssl_error_descr: A short description of the SSL error
+ * %ssl_lib_error: human-readable low-level error string by ErrorString()
+ *
+ * Certificate information extracted from broken (not necessarily peer!) cert
+ * %ssl_cn: The comma-separated list of common and alternate names
+ * %ssl_subject: The certificate subject
+ * %ssl_ca_name: The certificate issuer name
+ * %ssl_notbefore: The certificate "not before" field
+ * %ssl_notafter: The certificate "not after" field
+ *
+ \returns the length of the code (the number of characters to be replaced by value)
+ \retval 0 for unsupported codes
+*/
+size_t
+Security::ErrorDetail::convert(const char *code, const char **value) const
+{
+ typedef const char *(ErrorDetail::*PartDescriber)() const;
+ static const std::map<const char*, PartDescriber> PartDescriberByCode = {
+ {"ssl_subject", &ErrorDetail::subject},
+ {"ssl_ca_name", &ErrorDetail::ca_name},
+ {"ssl_cn", &ErrorDetail::cn},
+ {"ssl_notbefore", &ErrorDetail::notbefore},
+ {"ssl_notafter", &ErrorDetail::notafter},
+ {"err_name", &ErrorDetail::err_code},
+ {"ssl_error_descr", &ErrorDetail::err_descr},
+ {"ssl_lib_error", &ErrorDetail::err_lib_error}
+ };
+
+ for (const auto &pair: PartDescriberByCode) {
+ const auto len = strlen(pair.first);
+ if (strncmp(code, pair.first, len) == 0) {
+ const auto method = pair.second;
+ *value = (this->*method)();
+ return len;
+ }
+ }
+
+ // TODO: Support logformat %codes.
+ *value = ""; // unused with zero return
+ return 0;
+}
+
--- /dev/null
+/*
+ * 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
+
--- /dev/null
+/*
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+/* DEBUG: section 83 TLS I/O */
+
+#include "squid.h"
+#include "fde.h"
+#include "security/Io.h"
+
+namespace Security {
+
+template <typename Fun>
+static IoResult Handshake(Comm::Connection &, ErrorCode, Fun);
+static void PrepForIo();
+
+typedef SessionPointer::element_type *ConnectionPointer;
+
+} // namespace Security
+
+// TODO: Replace high-level ERR_get_error() calls with a new std::ostream
+// ReportErrors manipulator inside debugs(), followed by a ForgetErrors() call.
+void
+Security::ForgetErrors()
+{
+#if USE_OPENSSL
+ unsigned int reported = 0; // efficiently marks ForgetErrors() call boundary
+ while (const auto errorToForget = ERR_get_error())
+ debugs(83, 7, '#' << (++reported) << ": " << asHex(errorToForget));
+#endif
+}
+
+/// the steps necessary to perform before the upcoming TLS I/O
+/// to correctly interpret/detail the outcome of that I/O
+static void
+Security::PrepForIo()
+{
+ // flush earlier errors that some call forgot to extract, so that we will
+ // only get the error(s) specific to the upcoming I/O operation
+ ForgetErrors();
+
+ // as the last step, reset errno to know when the I/O operation set it
+ errno = 0;
+}
+
+/// Calls the given TLS handshake function and analysis its outcome.
+/// Handles alert logging and being called without adequate TLS library support.
+template <typename Fun>
+static Security::IoResult
+Security::Handshake(Comm::Connection &transport, const ErrorCode topError, Fun ioCall)
+{
+ assert(transport.isOpen());
+ const auto fd = transport.fd;
+ auto connection = fd_table[fd].ssl.get();
+
+ PrepForIo();
+ const auto callResult = ioCall(connection);
+ const auto xerrno = errno;
+
+ debugs(83, 5, callResult << '/' << xerrno << " for TLS connection " <<
+ static_cast<void*>(connection) << " over " << transport);
+
+#if USE_OPENSSL
+ if (callResult > 0)
+ return IoResult(IoResult::ioSuccess);
+
+ const auto ioError = SSL_get_error(connection, callResult);
+
+ // quickly handle common, non-erroneous outcomes
+ switch (ioError) {
+
+ case SSL_ERROR_WANT_READ:
+ return IoResult(IoResult::ioWantRead);
+
+ case SSL_ERROR_WANT_WRITE:
+ return IoResult(IoResult::ioWantWrite);
+
+ default:
+ ; // fall through to handle the problem
+ }
+
+ // now we know that we are dealing with a real problem; detail it
+ const ErrorDetail::Pointer errorDetail =
+ new ErrorDetail(topError, ioError, xerrno);
+
+ IoResult ioResult(errorDetail);
+
+ // collect debugging-related details
+ switch (ioError) {
+ case SSL_ERROR_SYSCALL:
+ if (callResult == 0) {
+ ioResult.errorDescription = "peer aborted";
+ } else {
+ ioResult.errorDescription = "system call failure";
+ ioResult.important = (xerrno == ECONNRESET);
+ }
+ break;
+
+ case SSL_ERROR_ZERO_RETURN:
+ // peer sent a "close notify" alert, closing TLS connection for writing
+ ioResult.errorDescription = "peer closed";
+ ioResult.important = true;
+ break;
+
+ default:
+ // an ever-increasing number of possible cases but usually SSL_ERROR_SSL
+ ioResult.errorDescription = "failure";
+ ioResult.important = true;
+ }
+
+ return ioResult;
+
+#elif USE_GNUTLS
+ if (callResult == GNUTLS_E_SUCCESS) {
+ // TODO: Avoid gnutls_*() calls if debugging is off.
+ const auto desc = gnutls_session_get_desc(connection);
+ debugs(83, 2, "TLS session info: " << desc);
+ gnutls_free(desc);
+ return IoResult(IoResult::ioSuccess);
+ }
+
+ // Debug the TLS connection state so far.
+ // TODO: Avoid gnutls_*() calls if debugging is off.
+ const auto descIn = gnutls_handshake_get_last_in(connection);
+ debugs(83, 2, "handshake IN: " << gnutls_handshake_description_get_name(descIn));
+ const auto descOut = gnutls_handshake_get_last_out(connection);
+ debugs(83, 2, "handshake OUT: " << gnutls_handshake_description_get_name(descOut));
+
+ if (callResult == GNUTLS_E_WARNING_ALERT_RECEIVED) {
+ const auto alert = gnutls_alert_get(connection);
+ debugs(83, DBG_IMPORTANT, "WARNING: TLS alert: " << gnutls_alert_get_name(alert));
+ // fall through to retry
+ }
+
+ if (!gnutls_error_is_fatal(callResult)) {
+ const auto reading = gnutls_record_get_direction(connection) == 0;
+ return IoResult(reading ? IoResult::ioWantRead : IoResult::ioWantWrite);
+ }
+
+ // now we know that we are dealing with a real problem; detail it
+ const ErrorDetail::Pointer errorDetail =
+ new ErrorDetail(topError, callResult, xerrno);
+
+ IoResult ioResult(errorDetail);
+ ioResult.errorDescription = "failure";
+ return ioResult;
+
+#else
+ // TLS I/O code path should never be reachable without a TLS/SSL library.
+ debugs(1, DBG_CRITICAL, ForceAlert << "BUG: " <<
+ "Unexpected TLS I/O in Squid built without a TLS/SSL library");
+ assert(false); // we want a stack trace which fatal() does not produce
+ return IoResult(nullptr); // not reachable
+#endif
+}
+
+// TODO: After dropping OpenSSL v1.1.0 support, this and Security::Connect() can
+// be simplified further by using SSL_do_handshake() and eliminating lambdas.
+Security::IoResult
+Security::Accept(Comm::Connection &transport)
+{
+ return Handshake(transport, SQUID_TLS_ERR_ACCEPT, [] (ConnectionPointer tlsConn) {
+#if USE_OPENSSL
+ return SSL_accept(tlsConn);
+#elif USE_GNUTLS
+ return gnutls_handshake(tlsConn);
+#else
+ return sizeof(tlsConn); // the value is unused; should be unreachable
+#endif
+ });
+}
+
+/// establish a TLS connection over the specified from-Squid transport connection
+Security::IoResult
+Security::Connect(Comm::Connection &transport)
+{
+ return Handshake(transport, SQUID_TLS_ERR_CONNECT, [] (ConnectionPointer tlsConn) {
+#if USE_OPENSSL
+ return SSL_connect(tlsConn);
+#elif USE_GNUTLS
+ return gnutls_handshake(tlsConn);
+#else
+ return sizeof(tlsConn); // the value is unused; should be unreachable
+#endif
+ });
+}
+
--- /dev/null
+/*
+ * 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 */
+
#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;
/// a helper label to simplify this objects API definitions below
typedef Security::LockingPointer<T, UnLocker, Locker> SelfType;
+ /// constructs a nil smart pointer
+ constexpr LockingPointer(): raw(nullptr) {}
+
+ /// constructs a nil smart pointer from nullptr
+ constexpr LockingPointer(std::nullptr_t): raw(nullptr) {}
+
/**
- * Construct directly from a raw pointer.
- * This action requires that the producer of that pointer has already
- * created one reference lock for the object pointed to.
- * Our destructor will do the matching unlock.
+ * Construct directly from a (possibly nil) raw pointer. If the supplied
+ * pointer is not nil, it is expected that its producer has already created
+ * one reference lock for the object pointed to, and our destructor will do
+ * the matching unlock.
*/
- explicit LockingPointer(T *t = nullptr): raw(nullptr) {
+ explicit LockingPointer(T *t): raw(nullptr) {
// de-optimized for clarity about non-locking
resetWithoutLocking(t);
}
Context.h \
EncryptorAnswer.cc \
EncryptorAnswer.h \
+ ErrorDetail.h \
+ ErrorDetail.cc \
Handshake.cc \
Handshake.h \
+ Io.cc \
+ Io.h \
KeyData.cc \
KeyData.h \
LockingPointer.h \
#include "HttpRequest.h"
#include "neighbors.h"
#include "pconn.h"
+#include "security/Io.h"
#include "security/NegotiationHistory.h"
#include "security/PeerConnector.h"
#include "SquidConfig.h"
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);
}
{
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);
}
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
{
Must(validationResponse != NULL);
- Ssl::ErrorDetail *errDetails = NULL;
+ ErrorDetail::Pointer errDetails;
bool validatorFailed = false;
if (Debug::Enabled(83, 5)) {
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*/
}
/// 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);
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;
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()
{
}
void
-Security::PeerConnector::noteNegotiationError(const int ret, const int ssl_error, const int ssl_lib_error)
+Security::PeerConnector::noteNegotiationError(const Security::ErrorDetailPointer &callerDetail)
{
-#if defined(EPROTO)
- int sysErrNo = EPROTO;
-#else
- int sysErrNo = EACCES;
-#endif
-
+ auto primaryDetail = callerDetail;
#if USE_OPENSSL
- // store/report errno when ssl_error is SSL_ERROR_SYSCALL, ssl_lib_error is 0, and ret is -1
- if (ssl_error == SSL_ERROR_SYSCALL && ret == -1 && ssl_lib_error == 0)
- sysErrNo = errno;
-#endif
- int xerr = errno;
-
- const int fd = serverConnection()->fd;
- debugs(83, DBG_IMPORTANT, "ERROR: negotiating TLS on FD " << fd <<
- ": " << Security::ErrorString(ssl_lib_error) << " (" <<
- ssl_error << "/" << ret << "/" << xerr << ")");
+ const auto tlsConnection = fd_table[serverConnection()->fd].ssl.get();
- const auto anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request, al);
- anErr->xerrno = sysErrNo;
-
-#if USE_OPENSSL
- Security::SessionPointer session(fd_table[fd].ssl);
- Ssl::ErrorDetail *errFromFailure = static_cast<Ssl::ErrorDetail *>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_error_detail));
- if (errFromFailure != NULL) {
- // The errFromFailure is attached to the ssl object
- // and will be released when ssl object destroyed.
- // Copy errFromFailure to a new Ssl::ErrorDetail object
- anErr->detail = new Ssl::ErrorDetail(*errFromFailure);
- } else {
- // server_cert can be NULL here
- X509 *server_cert = SSL_get_peer_certificate(session.get());
- anErr->detail = new Ssl::ErrorDetail(SQUID_ERR_SSL_HANDSHAKE, server_cert, NULL);
- X509_free(server_cert);
+ // find the highest priority detail
+ if (const auto storedDetailRaw = SSL_get_ex_data(tlsConnection, ssl_ex_index_ssl_error_detail)) {
+ const auto &storedDetail = *static_cast<ErrorDetail::Pointer*>(storedDetailRaw);
+ if (storedDetail->takesPriorityOver(*primaryDetail))
+ primaryDetail = storedDetail;
}
- if (ssl_lib_error != SSL_ERROR_NONE)
- anErr->detail->setLibError(ssl_lib_error);
+ if (!primaryDetail->peerCert()) {
+ if (const auto serverCert = SSL_get_peer_certificate(tlsConnection))
+ primaryDetail->setPeerCertificate(CertPointer(serverCert));
+ }
#endif
+ const auto anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request, al);
+ anErr->xerrno = primaryDetail->sysError();
+ anErr->detailError(primaryDetail);
noteNegotiationDone(anErr);
bail(anErr);
}
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.
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);
#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);
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);
}
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);
#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);
#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) {
#define SQUID_SRC_SECURITY_FORWARD_H
#include "base/CbDataList.h"
+#include "base/forward.h"
#include "security/Context.h"
#include "security/Session.h"
#include <gnutls/abstract.h>
#endif
#include <list>
+#include <limits>
#if USE_OPENSSL
#include "compat/openssl.h"
#if HAVE_OPENSSL_BN_H
/// Holds a list of X.509 certificate errors
typedef CbDataList<Security::CertError> CertErrors;
+#if USE_OPENSSL
+typedef X509 Certificate;
+#elif USE_GNUTLS
+typedef struct gnutls_x509_crt_int Certificate;
+#else
+typedef class {} Certificate;
+#endif
+
#if USE_OPENSSL
CtoCpp1(X509_free, X509 *);
typedef Security::LockingPointer<X509, X509_free_cpp, HardFun<int, X509 *, X509_up_ref> > CertPointer;
#elif USE_GNUTLS
typedef std::shared_ptr<struct gnutls_x509_crt_int> CertPointer;
#else
-typedef std::shared_ptr<void> CertPointer;
+typedef std::shared_ptr<Certificate> CertPointer;
#endif
#if USE_OPENSSL
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
} // namespace Io
+// TODO: Either move to Security::Io or remove/restrict the Io namespace.
+class IoResult;
+
class KeyData;
#if USE_OPENSSL
class ServerOptions;
+class ErrorDetail;
+typedef RefCount<ErrorDetail> ErrorDetailPointer;
+
} // namespace Security
+/// Squid-specific TLS handling errors (a subset of ErrorCode)
+/// These errors either distinguish high-level library calls/contexts or
+/// supplement official certificate validation errors to cover special cases.
+/// We use negative values, assuming that those official errors are positive.
+enum {
+ SQUID_TLS_ERR_OFFSET = std::numeric_limits<int>::min(),
+
+ /* TLS library calls/contexts other than validation (e.g., I/O) */
+ SQUID_TLS_ERR_ACCEPT, ///< failure to accept a connection from a TLS client
+ SQUID_TLS_ERR_CONNECT, ///< failure to establish a connection with a TLS server
+
+ /* certificate validation problems not covered by official errors */
+ SQUID_X509_V_ERR_CERT_CHANGE,
+ SQUID_X509_V_ERR_DOMAIN_MISMATCH,
+ SQUID_X509_V_ERR_INFINITE_VALIDATION,
+
+ SQUID_TLS_ERR_END
+};
+
#endif /* SQUID_SRC_SECURITY_FORWARD_H */
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;
}
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;
}
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;
}
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;
}
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;
}
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
#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"
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;
}
// 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;
}
#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"
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();
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
};
#include "squid.h"
#include "errorpage.h"
#include "fatal.h"
-#include "html_quote.h"
#include "ssl/ErrorDetail.h"
+#include "ssl/ErrorDetailManager.h"
-#include <climits>
#include <map>
-struct SslErrorEntry {
- Security::ErrorCode value;
- const char *name;
-};
-
-static const char *SslErrorDetailDefaultStr = "SSL handshake error (%err_name)";
-//Use std::map to optimize search
-typedef std::map<Security::ErrorCode, const SslErrorEntry *> SslErrors;
-SslErrors TheSslErrors;
-
-static SslErrorEntry TheSslErrorArray[] = {
- { SQUID_X509_V_ERR_INFINITE_VALIDATION,
- "SQUID_X509_V_ERR_INFINITE_VALIDATION"
- },
- { SQUID_X509_V_ERR_CERT_CHANGE,
- "SQUID_X509_V_ERR_CERT_CHANGE"
- },
- { SQUID_ERR_SSL_HANDSHAKE,
- "SQUID_ERR_SSL_HANDSHAKE"
- },
- { SQUID_X509_V_ERR_DOMAIN_MISMATCH,
- "SQUID_X509_V_ERR_DOMAIN_MISMATCH"
- },
- { X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT,
- "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT"
- },
- { X509_V_ERR_UNABLE_TO_GET_CRL,
- "X509_V_ERR_UNABLE_TO_GET_CRL"
- },
- { X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE,
- "X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE"
- },
- { X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE,
- "X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE"
- },
- { X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY,
- "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY"
- },
- { X509_V_ERR_CERT_SIGNATURE_FAILURE,
- "X509_V_ERR_CERT_SIGNATURE_FAILURE"
- },
- { X509_V_ERR_CRL_SIGNATURE_FAILURE,
- "X509_V_ERR_CRL_SIGNATURE_FAILURE"
- },
- { X509_V_ERR_CERT_NOT_YET_VALID,
- "X509_V_ERR_CERT_NOT_YET_VALID"
- },
- { X509_V_ERR_CERT_HAS_EXPIRED,
- "X509_V_ERR_CERT_HAS_EXPIRED"
- },
- { X509_V_ERR_CRL_NOT_YET_VALID,
- "X509_V_ERR_CRL_NOT_YET_VALID"
- },
- { X509_V_ERR_CRL_HAS_EXPIRED,
- "X509_V_ERR_CRL_HAS_EXPIRED"
- },
- { X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD,
- "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD"
- },
- { X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD,
- "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD"
- },
- { X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD,
- "X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD"
- },
- { X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD,
- "X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD"
- },
- { X509_V_ERR_OUT_OF_MEM,
- "X509_V_ERR_OUT_OF_MEM"
- },
- { X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,
- "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
- },
- { X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN,
- "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN"
- },
- { X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
- "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
- },
- { X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE,
- "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
- },
- { X509_V_ERR_CERT_CHAIN_TOO_LONG,
- "X509_V_ERR_CERT_CHAIN_TOO_LONG"
- },
- { X509_V_ERR_CERT_REVOKED,
- "X509_V_ERR_CERT_REVOKED"
- },
- { X509_V_ERR_INVALID_CA,
- "X509_V_ERR_INVALID_CA"
- },
- { X509_V_ERR_PATH_LENGTH_EXCEEDED,
- "X509_V_ERR_PATH_LENGTH_EXCEEDED"
- },
- { X509_V_ERR_INVALID_PURPOSE,
- "X509_V_ERR_INVALID_PURPOSE"
- },
- { X509_V_ERR_CERT_UNTRUSTED,
- "X509_V_ERR_CERT_UNTRUSTED"
- },
- { X509_V_ERR_CERT_REJECTED,
- "X509_V_ERR_CERT_REJECTED"
- },
- { X509_V_ERR_SUBJECT_ISSUER_MISMATCH,
- "X509_V_ERR_SUBJECT_ISSUER_MISMATCH"
- },
- { X509_V_ERR_AKID_SKID_MISMATCH,
- "X509_V_ERR_AKID_SKID_MISMATCH"
- },
- { X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH,
- "X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH"
- },
- { X509_V_ERR_KEYUSAGE_NO_CERTSIGN,
- "X509_V_ERR_KEYUSAGE_NO_CERTSIGN"
- },
-#if defined(X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER)
- {
- X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER, //33
- "X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER"
- },
-#endif
-#if defined(X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION)
- {
- X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION, //34
- "X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION"
- },
-#endif
-#if defined(X509_V_ERR_KEYUSAGE_NO_CRL_SIGN)
- {
- X509_V_ERR_KEYUSAGE_NO_CRL_SIGN, //35
- "X509_V_ERR_KEYUSAGE_NO_CRL_SIGN"
- },
-#endif
-#if defined(X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION)
- {
- X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION, //36
- "X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION"
- },
-#endif
-#if defined(X509_V_ERR_INVALID_NON_CA)
- {
- X509_V_ERR_INVALID_NON_CA, //37
- "X509_V_ERR_INVALID_NON_CA"
- },
-#endif
-#if defined(X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED)
- {
- X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED, //38
- "X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED"
- },
-#endif
-#if defined(X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE)
- {
- X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE, //39
- "X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE"
- },
-#endif
-#if defined(X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED)
- {
- X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED, //40
- "X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED"
- },
-#endif
-#if defined(X509_V_ERR_INVALID_EXTENSION)
- {
- X509_V_ERR_INVALID_EXTENSION, //41
- "X509_V_ERR_INVALID_EXTENSION"
- },
-#endif
-#if defined(X509_V_ERR_INVALID_POLICY_EXTENSION)
- {
- X509_V_ERR_INVALID_POLICY_EXTENSION, //42
- "X509_V_ERR_INVALID_POLICY_EXTENSION"
- },
-#endif
-#if defined(X509_V_ERR_NO_EXPLICIT_POLICY)
- {
- X509_V_ERR_NO_EXPLICIT_POLICY, //43
- "X509_V_ERR_NO_EXPLICIT_POLICY"
- },
-#endif
-#if defined(X509_V_ERR_DIFFERENT_CRL_SCOPE)
- {
- X509_V_ERR_DIFFERENT_CRL_SCOPE, //44
- "X509_V_ERR_DIFFERENT_CRL_SCOPE"
- },
-#endif
-#if defined(X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE)
- {
- X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE, //45
- "X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE"
- },
-#endif
-#if defined(X509_V_ERR_UNNESTED_RESOURCE)
- {
- X509_V_ERR_UNNESTED_RESOURCE, //46
- "X509_V_ERR_UNNESTED_RESOURCE"
- },
-#endif
-#if defined(X509_V_ERR_PERMITTED_VIOLATION)
- {
- X509_V_ERR_PERMITTED_VIOLATION, //47
- "X509_V_ERR_PERMITTED_VIOLATION"
- },
-#endif
-#if defined(X509_V_ERR_EXCLUDED_VIOLATION)
- {
- X509_V_ERR_EXCLUDED_VIOLATION, //48
- "X509_V_ERR_EXCLUDED_VIOLATION"
- },
-#endif
-#if defined(X509_V_ERR_SUBTREE_MINMAX)
- {
- X509_V_ERR_SUBTREE_MINMAX, //49
- "X509_V_ERR_SUBTREE_MINMAX"
- },
-#endif
- { X509_V_ERR_APPLICATION_VERIFICATION, //50
- "X509_V_ERR_APPLICATION_VERIFICATION"
- },
-#if defined(X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE)
- {
- X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE, //51
- "X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE"
- },
-#endif
-#if defined(X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX)
- {
- X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX, //52
- "X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX"
- },
-#endif
-#if defined(X509_V_ERR_UNSUPPORTED_NAME_SYNTAX)
- {
- X509_V_ERR_UNSUPPORTED_NAME_SYNTAX, //53
- "X509_V_ERR_UNSUPPORTED_NAME_SYNTAX"
- },
-#endif
-#if defined(X509_V_ERR_CRL_PATH_VALIDATION_ERROR)
- {
- X509_V_ERR_CRL_PATH_VALIDATION_ERROR, //54
- "X509_V_ERR_CRL_PATH_VALIDATION_ERROR"
- },
-#endif
-#if defined(X509_V_ERR_PATH_LOOP)
- {
- X509_V_ERR_PATH_LOOP, //55
- "X509_V_ERR_PATH_LOOP"
- },
-#endif
-#if defined(X509_V_ERR_SUITE_B_INVALID_VERSION)
- {
- X509_V_ERR_SUITE_B_INVALID_VERSION, //56
- "X509_V_ERR_SUITE_B_INVALID_VERSION"
- },
-#endif
-#if defined(X509_V_ERR_SUITE_B_INVALID_ALGORITHM)
- {
- X509_V_ERR_SUITE_B_INVALID_ALGORITHM, //57
- "X509_V_ERR_SUITE_B_INVALID_ALGORITHM"
- },
-#endif
-#if defined(X509_V_ERR_SUITE_B_INVALID_CURVE)
- {
- X509_V_ERR_SUITE_B_INVALID_CURVE, //58
- "X509_V_ERR_SUITE_B_INVALID_CURVE"
- },
-#endif
-#if defined(X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM)
- {
- X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM, //59
- "X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM"
- },
-#endif
-#if defined(X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED)
- {
- X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED, //60
- "X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED"
- },
-#endif
-#if defined(X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256)
- {
- X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256, //61
- "X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256"
- },
-#endif
-#if defined(X509_V_ERR_HOSTNAME_MISMATCH)
- {
- X509_V_ERR_HOSTNAME_MISMATCH, //62
- "X509_V_ERR_HOSTNAME_MISMATCH"
- },
-#endif
-#if defined(X509_V_ERR_EMAIL_MISMATCH)
- {
- X509_V_ERR_EMAIL_MISMATCH, //63
- "X509_V_ERR_EMAIL_MISMATCH"
- },
-#endif
-#if defined(X509_V_ERR_IP_ADDRESS_MISMATCH)
- {
- X509_V_ERR_IP_ADDRESS_MISMATCH, //64
- "X509_V_ERR_IP_ADDRESS_MISMATCH"
- },
-#endif
-#if defined(X509_V_ERR_DANE_NO_MATCH)
- {
- X509_V_ERR_DANE_NO_MATCH, //65
- "X509_V_ERR_DANE_NO_MATCH"
- },
-#endif
-#if defined(X509_V_ERR_EE_KEY_TOO_SMALL)
- {
- X509_V_ERR_EE_KEY_TOO_SMALL, //66
- "X509_V_ERR_EE_KEY_TOO_SMALL"
- },
-#endif
-#if defined(X509_V_ERR_CA_KEY_TOO_SMALL)
- {
- X509_V_ERR_CA_KEY_TOO_SMALL, //67
- "X509_V_ERR_CA_KEY_TOO_SMALL"
- },
-#endif
-#if defined(X509_V_ERR_CA_MD_TOO_WEAK)
- {
- X509_V_ERR_CA_MD_TOO_WEAK, //68
- "X509_V_ERR_CA_MD_TOO_WEAK"
- },
-#endif
-#if defined(X509_V_ERR_INVALID_CALL)
- {
- X509_V_ERR_INVALID_CALL, //69
- "X509_V_ERR_INVALID_CALL"
- },
-#endif
-#if defined(X509_V_ERR_STORE_LOOKUP)
- {
- X509_V_ERR_STORE_LOOKUP, //70
- "X509_V_ERR_STORE_LOOKUP"
- },
-#endif
-#if defined(X509_V_ERR_NO_VALID_SCTS)
- {
- X509_V_ERR_NO_VALID_SCTS, //71
- "X509_V_ERR_NO_VALID_SCTS"
- },
-#endif
-#if defined(X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION)
- {
- X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION, //72
- "X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION"
- },
-#endif
-#if defined(X509_V_ERR_OCSP_VERIFY_NEEDED)
- {
- X509_V_ERR_OCSP_VERIFY_NEEDED, //73
- "X509_V_ERR_OCSP_VERIFY_NEEDED"
- },
-#endif
-#if defined(X509_V_ERR_OCSP_VERIFY_FAILED)
- {
- X509_V_ERR_OCSP_VERIFY_FAILED, //74
- "X509_V_ERR_OCSP_VERIFY_FAILED"
- },
-#endif
-#if defined(X509_V_ERR_OCSP_CERT_UNKNOWN)
- {
- X509_V_ERR_OCSP_CERT_UNKNOWN, //75
- "X509_V_ERR_OCSP_CERT_UNKNOWN"
- },
-#endif
- { SSL_ERROR_NONE, "SSL_ERROR_NONE"},
- {SSL_ERROR_NONE, NULL}
-};
-
static const char *OptionalSslErrors[] = {
"X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER",
"X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION",
typedef std::map<std::string, const Security::ErrorCode *> SslErrorShortcuts;
SslErrorShortcuts TheSslErrorShortcuts;
-static void loadSslErrorMap()
-{
- assert(TheSslErrors.empty());
- for (int i = 0; TheSslErrorArray[i].name; ++i) {
- TheSslErrors[TheSslErrorArray[i].value] = &TheSslErrorArray[i];
- }
-}
-
static void loadSslErrorShortcutsMap()
{
assert(TheSslErrorShortcuts.empty());
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)
{
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;
}
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)
{
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;
-}
-
#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
{
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);
/// \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
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)
#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"
if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) {
if (!serverBump->serverCert.get()) {
// remember the server certificate from the ErrorDetail object
- if (error && error->detail && error->detail->peerCert())
- serverBump->serverCert.resetAndLock(error->detail->peerCert());
+ const auto errDetail = dynamic_cast<Security::ErrorDetail *>(error ? error->detail.getRaw() : nullptr);
+ if (errDetail && errDetail->peerCert())
+ serverBump->serverCert.resetAndLock(errDetail->peerCert());
else {
handleServerCertificate();
}
}
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);
(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
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
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)
{
class RecvdError
{
public:
- RecvdError(): id(0), error_no(SSL_ERROR_NONE), cert(NULL), error_depth(-1) {}
- RecvdError(const RecvdError &);
- RecvdError & operator =(const RecvdError &);
void setCert(X509 *); ///< Sets cert to the given certificate
- int id; ///< The id of the error
- Security::ErrorCode error_no; ///< The OpenSSL error code
+ int id = 0; ///< The id of the error
+ Security::ErrorCode error_no = 0; ///< The OpenSSL error code
std::string error_reason; ///< A string describing the error
Security::CertPointer cert; ///< The broken certificate
- int error_depth; ///< The error depth
+ int error_depth = -1; ///< The error depth
};
typedef std::vector<RecvdError> RecvdErrors;
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
};
#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"
broken_cert.resetAndLock(last_used_cert);
}
- auto *errDetail = new Ssl::ErrorDetail(error_no, peer_cert.get(), broken_cert.get());
- if (!SSL_set_ex_data(ssl, ssl_ex_index_ssl_error_detail, errDetail)) {
- debugs(83, 2, "Failed to set Ssl::ErrorDetail in ssl_verify_cb: Certificate " << buffer);
- delete errDetail;
- }
+ std::unique_ptr<Security::ErrorDetail::Pointer> edp(new Security::ErrorDetail::Pointer(
+ new Security::ErrorDetail(error_no, peer_cert, broken_cert)));
+ if (SSL_set_ex_data(ssl, ssl_ex_index_ssl_error_detail, edp.get()))
+ edp.release();
+ else
+ debugs(83, 2, "failed to store a " << buffer << " error detail: " << *edp);
}
return ok;
ssl_free_ErrorDetail(void *, void *ptr, CRYPTO_EX_DATA *,
int, long, void *)
{
- Ssl::ErrorDetail *errDetail = static_cast <Ssl::ErrorDetail *>(ptr);
+ const auto errDetail = static_cast<Security::ErrorDetail::Pointer*>(ptr);
delete errDetail;
}
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));
}
}
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));
}
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));
}
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));
}
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));
}
// 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));
}
// 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");
}
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);
\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.
/// call before generating any SSL context
void Initialize();
-class ErrorDetail;
class CertValidationResponse;
typedef RefCount<CertValidationResponse> CertValidationResponsePointer;
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
--- /dev/null
+#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())
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
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
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
#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