]> git.ipfire.org Git - thirdparty/squid.git/blob - src/security/Io.cc
Detail client closures of CONNECT tunnels during TLS handshake (#691)
[thirdparty/squid.git] / src / security / Io.cc
1 /*
2 * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
3 *
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.
7 */
8
9 /* DEBUG: section 83 TLS I/O */
10
11 #include "squid.h"
12 #include "fde.h"
13 #include "security/Io.h"
14
15 namespace Security {
16
17 template <typename Fun>
18 static IoResult Handshake(Comm::Connection &, ErrorCode, Fun);
19 static void PrepForIo();
20
21 typedef SessionPointer::element_type *ConnectionPointer;
22
23 } // namespace Security
24
25 // TODO: Replace high-level ERR_get_error() calls with a new std::ostream
26 // ReportErrors manipulator inside debugs(), followed by a ForgetErrors() call.
27 void
28 Security::ForgetErrors()
29 {
30 #if USE_OPENSSL
31 unsigned int reported = 0; // efficiently marks ForgetErrors() call boundary
32 while (const auto errorToForget = ERR_get_error())
33 debugs(83, 7, '#' << (++reported) << ": " << asHex(errorToForget));
34 #endif
35 }
36
37 /// the steps necessary to perform before the upcoming TLS I/O
38 /// to correctly interpret/detail the outcome of that I/O
39 static void
40 Security::PrepForIo()
41 {
42 // flush earlier errors that some call forgot to extract, so that we will
43 // only get the error(s) specific to the upcoming I/O operation
44 ForgetErrors();
45
46 // as the last step, reset errno to know when the I/O operation set it
47 errno = 0;
48 }
49
50 /// Calls the given TLS handshake function and analysis its outcome.
51 /// Handles alert logging and being called without adequate TLS library support.
52 template <typename Fun>
53 static Security::IoResult
54 Security::Handshake(Comm::Connection &transport, const ErrorCode topError, Fun ioCall)
55 {
56 assert(transport.isOpen());
57 const auto fd = transport.fd;
58 auto connection = fd_table[fd].ssl.get();
59
60 PrepForIo();
61 const auto callResult = ioCall(connection);
62 const auto xerrno = errno;
63
64 debugs(83, 5, callResult << '/' << xerrno << " for TLS connection " <<
65 static_cast<void*>(connection) << " over " << transport);
66
67 #if USE_OPENSSL
68 if (callResult > 0)
69 return IoResult(IoResult::ioSuccess);
70
71 const auto ioError = SSL_get_error(connection, callResult);
72
73 // quickly handle common, non-erroneous outcomes
74 switch (ioError) {
75
76 case SSL_ERROR_WANT_READ:
77 return IoResult(IoResult::ioWantRead);
78
79 case SSL_ERROR_WANT_WRITE:
80 return IoResult(IoResult::ioWantWrite);
81
82 default:
83 ; // fall through to handle the problem
84 }
85
86 // now we know that we are dealing with a real problem; detail it
87 const ErrorDetail::Pointer errorDetail =
88 new ErrorDetail(topError, ioError, xerrno);
89
90 IoResult ioResult(errorDetail);
91
92 // collect debugging-related details
93 switch (ioError) {
94 case SSL_ERROR_SYSCALL:
95 if (callResult == 0) {
96 ioResult.errorDescription = "peer aborted";
97 } else {
98 ioResult.errorDescription = "system call failure";
99 ioResult.important = (xerrno == ECONNRESET);
100 }
101 break;
102
103 case SSL_ERROR_ZERO_RETURN:
104 // peer sent a "close notify" alert, closing TLS connection for writing
105 ioResult.errorDescription = "peer closed";
106 ioResult.important = true;
107 break;
108
109 default:
110 // an ever-increasing number of possible cases but usually SSL_ERROR_SSL
111 ioResult.errorDescription = "failure";
112 ioResult.important = true;
113 }
114
115 return ioResult;
116
117 #elif USE_GNUTLS
118 if (callResult == GNUTLS_E_SUCCESS) {
119 // TODO: Avoid gnutls_*() calls if debugging is off.
120 const auto desc = gnutls_session_get_desc(connection);
121 debugs(83, 2, "TLS session info: " << desc);
122 gnutls_free(desc);
123 return IoResult(IoResult::ioSuccess);
124 }
125
126 // Debug the TLS connection state so far.
127 // TODO: Avoid gnutls_*() calls if debugging is off.
128 const auto descIn = gnutls_handshake_get_last_in(connection);
129 debugs(83, 2, "handshake IN: " << gnutls_handshake_description_get_name(descIn));
130 const auto descOut = gnutls_handshake_get_last_out(connection);
131 debugs(83, 2, "handshake OUT: " << gnutls_handshake_description_get_name(descOut));
132
133 if (callResult == GNUTLS_E_WARNING_ALERT_RECEIVED) {
134 const auto alert = gnutls_alert_get(connection);
135 debugs(83, DBG_IMPORTANT, "WARNING: TLS alert: " << gnutls_alert_get_name(alert));
136 // fall through to retry
137 }
138
139 if (!gnutls_error_is_fatal(callResult)) {
140 const auto reading = gnutls_record_get_direction(connection) == 0;
141 return IoResult(reading ? IoResult::ioWantRead : IoResult::ioWantWrite);
142 }
143
144 // now we know that we are dealing with a real problem; detail it
145 const ErrorDetail::Pointer errorDetail =
146 new ErrorDetail(topError, callResult, xerrno);
147
148 IoResult ioResult(errorDetail);
149 ioResult.errorDescription = "failure";
150 return ioResult;
151
152 #else
153 // TLS I/O code path should never be reachable without a TLS/SSL library.
154 debugs(1, DBG_CRITICAL, ForceAlert << "BUG: " <<
155 "Unexpected TLS I/O in Squid built without a TLS/SSL library");
156 assert(false); // we want a stack trace which fatal() does not produce
157 return IoResult(nullptr); // not reachable
158 #endif
159 }
160
161 // TODO: After dropping OpenSSL v1.1.0 support, this and Security::Connect() can
162 // be simplified further by using SSL_do_handshake() and eliminating lambdas.
163 Security::IoResult
164 Security::Accept(Comm::Connection &transport)
165 {
166 return Handshake(transport, SQUID_TLS_ERR_ACCEPT, [] (ConnectionPointer tlsConn) {
167 #if USE_OPENSSL
168 return SSL_accept(tlsConn);
169 #elif USE_GNUTLS
170 return gnutls_handshake(tlsConn);
171 #else
172 return sizeof(tlsConn); // the value is unused; should be unreachable
173 #endif
174 });
175 }
176
177 /// establish a TLS connection over the specified from-Squid transport connection
178 Security::IoResult
179 Security::Connect(Comm::Connection &transport)
180 {
181 return Handshake(transport, SQUID_TLS_ERR_CONNECT, [] (ConnectionPointer tlsConn) {
182 #if USE_OPENSSL
183 return SSL_connect(tlsConn);
184 #elif USE_GNUTLS
185 return gnutls_handshake(tlsConn);
186 #else
187 return sizeof(tlsConn); // the value is unused; should be unreachable
188 #endif
189 });
190 }
191