2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
9 /* DEBUG: section 83 TLS I/O */
12 #include "base/IoManip.h"
14 #include "security/Io.h"
15 #include "ssl/gadgets.h"
19 template <typename Fun
>
20 static IoResult
Handshake(Comm::Connection
&, ErrorCode
, Fun
);
21 static void PrepForIo();
23 typedef SessionPointer::element_type
*ConnectionPointer
;
25 } // namespace Security
28 Security::IoResult::print(std::ostream
&os
) const
30 const char *strCat
= "unknown";
39 strCat
= "want-write";
48 os
<< ", " << errorDescription
;
54 // TODO: Replace high-level ERR_get_error() calls with ForgetErrors() calls or
55 // exceptions carrying ReportAndForgetErrors() reports.
57 Security::ForgetErrors()
64 /// the steps necessary to perform before the upcoming TLS I/O
65 /// to correctly interpret/detail the outcome of that I/O
69 // flush earlier errors that some call forgot to extract, so that we will
70 // only get the error(s) specific to the upcoming I/O operation
73 // as the last step, reset errno to know when the I/O operation set it
77 /// Calls the given TLS handshake function and analysis its outcome.
78 /// Handles alert logging and being called without adequate TLS library support.
79 template <typename Fun
>
80 static Security::IoResult
81 Security::Handshake(Comm::Connection
&transport
, const ErrorCode topError
, Fun ioCall
)
83 assert(transport
.isOpen());
84 const auto fd
= transport
.fd
;
85 auto connection
= fd_table
[fd
].ssl
.get();
88 const auto callResult
= ioCall(connection
);
89 const auto xerrno
= errno
;
91 debugs(83, 5, callResult
<< '/' << xerrno
<< " for TLS connection " <<
92 static_cast<void*>(connection
) << " over " << transport
);
96 return IoResult(IoResult::ioSuccess
);
98 const auto ioError
= SSL_get_error(connection
, callResult
);
100 // quickly handle common, non-erroneous outcomes
103 case SSL_ERROR_WANT_READ
:
104 return IoResult(IoResult::ioWantRead
);
106 case SSL_ERROR_WANT_WRITE
:
107 return IoResult(IoResult::ioWantWrite
);
110 ; // fall through to handle the problem
113 // now we know that we are dealing with a real problem; detail it
114 ErrorDetail::Pointer errorDetail
;
115 if (const auto oldDetail
= SSL_get_ex_data(connection
, ssl_ex_index_ssl_error_detail
)) {
116 errorDetail
= *static_cast<ErrorDetail::Pointer
*>(oldDetail
);
118 errorDetail
= new ErrorDetail(topError
, ioError
, xerrno
);
119 if (const auto serverCert
= SSL_get_peer_certificate(connection
))
120 errorDetail
->setPeerCertificate(CertPointer(serverCert
));
122 IoResult
ioResult(errorDetail
);
124 // collect debugging-related details
126 case SSL_ERROR_SYSCALL
:
127 if (callResult
== 0) {
128 ioResult
.errorDescription
= "peer aborted";
130 ioResult
.errorDescription
= "system call failure";
131 ioResult
.important
= (xerrno
== ECONNRESET
);
135 case SSL_ERROR_ZERO_RETURN
:
136 // peer sent a "close notify" alert, closing TLS connection for writing
137 ioResult
.errorDescription
= "peer closed";
138 ioResult
.important
= true;
142 // an ever-increasing number of possible cases but usually SSL_ERROR_SSL
143 ioResult
.errorDescription
= "failure";
144 ioResult
.important
= true;
150 if (callResult
== GNUTLS_E_SUCCESS
) {
151 // TODO: Avoid gnutls_*() calls if debugging is off.
152 const auto desc
= gnutls_session_get_desc(connection
);
153 debugs(83, 2, "TLS session info: " << desc
);
155 return IoResult(IoResult::ioSuccess
);
158 // Debug the TLS connection state so far.
159 // TODO: Avoid gnutls_*() calls if debugging is off.
160 const auto descIn
= gnutls_handshake_get_last_in(connection
);
161 debugs(83, 2, "handshake IN: " << gnutls_handshake_description_get_name(descIn
));
162 const auto descOut
= gnutls_handshake_get_last_out(connection
);
163 debugs(83, 2, "handshake OUT: " << gnutls_handshake_description_get_name(descOut
));
165 if (callResult
== GNUTLS_E_WARNING_ALERT_RECEIVED
) {
166 const auto alert
= gnutls_alert_get(connection
);
167 debugs(83, DBG_IMPORTANT
, "WARNING: TLS alert: " << gnutls_alert_get_name(alert
));
168 // fall through to retry
171 if (!gnutls_error_is_fatal(callResult
)) {
172 const auto reading
= gnutls_record_get_direction(connection
) == 0;
173 return IoResult(reading
? IoResult::ioWantRead
: IoResult::ioWantWrite
);
176 // now we know that we are dealing with a real problem; detail it
177 const ErrorDetail::Pointer errorDetail
=
178 new ErrorDetail(topError
, callResult
, xerrno
);
180 IoResult
ioResult(errorDetail
);
181 ioResult
.errorDescription
= "failure";
186 // TLS I/O code path should never be reachable without a TLS/SSL library.
187 debugs(1, DBG_CRITICAL
, ForceAlert
<< "ERROR: Squid BUG: " <<
188 "Unexpected TLS I/O in Squid built without a TLS/SSL library");
189 assert(false); // we want a stack trace which fatal() does not produce
190 return IoResult(nullptr); // not reachable
194 // TODO: After dropping OpenSSL v1.1.0 support, this and Security::Connect() can
195 // be simplified further by using SSL_do_handshake() and eliminating lambdas.
197 Security::Accept(Comm::Connection
&transport
)
199 return Handshake(transport
, SQUID_TLS_ERR_ACCEPT
, [] (ConnectionPointer tlsConn
) {
201 return SSL_accept(tlsConn
);
203 return gnutls_handshake(tlsConn
);
205 return sizeof(tlsConn
); // the value is unused; should be unreachable
210 /// establish a TLS connection over the specified from-Squid transport connection
212 Security::Connect(Comm::Connection
&transport
)
214 return Handshake(transport
, SQUID_TLS_ERR_CONNECT
, [] (ConnectionPointer tlsConn
) {
216 return SSL_connect(tlsConn
);
218 return gnutls_handshake(tlsConn
);
220 return sizeof(tlsConn
); // the value is unused; should be unreachable