Io.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2025 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 "base/IoManip.h"
13 #include "fde.h"
14 #include "security/Io.h"
15 #include "ssl/gadgets.h"
16 
17 namespace Security {
18 
19 template <typename Fun>
21 
22 typedef SessionPointer::element_type *ConnectionPointer;
23 
24 } // namespace Security
25 
27 void
28 Security::IoResult::printDescription(std::ostream &os) const
29 {
30  const char *strCat = nullptr;
31  switch (category) {
32  case ioSuccess:
33  strCat = "success";
34  break;
35  case ioWantRead:
36  strCat = "want-read";
37  break;
38  case ioWantWrite:
39  strCat = "want-write";
40  break;
41  case ioError:
42  strCat = errorDescription;
43  break;
44  }
45  os << (strCat ? strCat : "unknown");
46 }
47 
48 void
49 Security::IoResult::printGist(std::ostream &os) const
50 {
51  printDescription(os);
52  if (important)
53  os << ", important";
54  // no errorDetail in this summary output
55 }
56 
57 void
58 Security::IoResult::printWithExtras(std::ostream &os) const
59 {
60  printDescription(os);
61  if (errorDetail)
62  os << Debug::Extra << "error detail: " << errorDetail;
63  // this->important flag may affect caller debugs() level, but the flag
64  // itself is not reported to the admin explicitly
65 }
66 
67 // TODO: Replace high-level ERR_get_error() calls with ForgetErrors() calls or
68 // exceptions carrying ReportAndForgetErrors() reports.
69 void
71 {
72 #if USE_OPENSSL
74 #endif
75 }
76 
77 void
79 {
80  // flush earlier errors that some call forgot to extract, so that we will
81  // only get the error(s) specific to the upcoming I/O operation
82  ForgetErrors();
83 
84  // as the last step, reset errno to know when the I/O operation set it
85  errno = 0;
86 }
87 
90 template <typename Fun>
91 static Security::IoResult
92 Security::Handshake(Comm::Connection &transport, const ErrorCode topError, Fun ioCall)
93 {
94  assert(transport.isOpen());
95  const auto fd = transport.fd;
96  auto connection = fd_table[fd].ssl.get();
97 
98  PrepForIo();
99  const auto callResult = ioCall(connection);
100  const auto xerrno = errno;
101 
102  debugs(83, 5, callResult << '/' << xerrno << " for TLS connection " <<
103  static_cast<void*>(connection) << " over " << transport);
104 
105 #if USE_OPENSSL
106  if (callResult > 0)
108 
109  const auto ioError = SSL_get_error(connection, callResult);
110 
111  // quickly handle common, non-erroneous outcomes
112  switch (ioError) {
113 
114  case SSL_ERROR_WANT_READ:
116 
117  case SSL_ERROR_WANT_WRITE:
119 
120  default:
121  ; // fall through to handle the problem
122  }
123 
124  // now we know that we are dealing with a real problem; detail it
125  ErrorDetail::Pointer errorDetail;
126  if (const auto oldDetail = SSL_get_ex_data(connection, ssl_ex_index_ssl_error_detail)) {
127  errorDetail = *static_cast<ErrorDetail::Pointer*>(oldDetail);
128  } else {
129  errorDetail = new ErrorDetail(topError, ioError, xerrno);
130  if (const auto serverCert = SSL_get_peer_certificate(connection))
131  errorDetail->setPeerCertificate(CertPointer(serverCert));
132  }
133  IoResult ioResult(errorDetail);
134 
135  // collect debugging-related details
136  switch (ioError) {
137  case SSL_ERROR_SYSCALL:
138  if (callResult == 0) {
139  ioResult.errorDescription = "peer aborted";
140  } else {
141  ioResult.errorDescription = "system call failure";
142  ioResult.important = (xerrno == ECONNRESET);
143  }
144  break;
145 
146  case SSL_ERROR_ZERO_RETURN:
147  // peer sent a "close notify" alert, closing TLS connection for writing
148  ioResult.errorDescription = "peer closed";
149  ioResult.important = true;
150  break;
151 
152  default:
153  // an ever-increasing number of possible cases but usually SSL_ERROR_SSL
154  ioResult.errorDescription = "failure";
155  ioResult.important = true;
156  }
157 
158  return ioResult;
159 
160 #elif HAVE_LIBGNUTLS
161  if (callResult == GNUTLS_E_SUCCESS) {
162  // TODO: Avoid gnutls_*() calls if debugging is off.
163  const auto desc = gnutls_session_get_desc(connection);
164  debugs(83, 2, "TLS session info: " << desc);
165  gnutls_free(desc);
167  }
168 
169  // Debug the TLS connection state so far.
170  // TODO: Avoid gnutls_*() calls if debugging is off.
171  const auto descIn = gnutls_handshake_get_last_in(connection);
172  debugs(83, 2, "handshake IN: " << gnutls_handshake_description_get_name(descIn));
173  const auto descOut = gnutls_handshake_get_last_out(connection);
174  debugs(83, 2, "handshake OUT: " << gnutls_handshake_description_get_name(descOut));
175 
176  if (callResult == GNUTLS_E_WARNING_ALERT_RECEIVED) {
177  const auto alert = gnutls_alert_get(connection);
178  debugs(83, DBG_IMPORTANT, "WARNING: TLS alert: " << gnutls_alert_get_name(alert));
179  // fall through to retry
180  }
181 
182  if (!gnutls_error_is_fatal(callResult)) {
183  const auto reading = gnutls_record_get_direction(connection) == 0;
185  }
186 
187  // now we know that we are dealing with a real problem; detail it
188  const ErrorDetail::Pointer errorDetail =
189  new ErrorDetail(topError, callResult, xerrno);
190 
191  IoResult ioResult(errorDetail);
192  ioResult.errorDescription = "failure";
193  return ioResult;
194 
195 #else
196  (void)topError;
197  // TLS I/O code path should never be reachable without a TLS/SSL library.
198  debugs(1, DBG_CRITICAL, ForceAlert << "ERROR: Squid BUG: " <<
199  "Unexpected TLS I/O in Squid built without a TLS/SSL library");
200  assert(false); // we want a stack trace which fatal() does not produce
201  return IoResult(nullptr); // not reachable
202 #endif
203 }
204 
205 // TODO: After dropping OpenSSL v1.1.0 support, this and Security::Connect() can
206 // be simplified further by using SSL_do_handshake() and eliminating lambdas.
209 {
210  return Handshake(transport, SQUID_TLS_ERR_ACCEPT, [] (ConnectionPointer tlsConn) {
211 #if USE_OPENSSL
212  return SSL_accept(tlsConn);
213 #elif HAVE_LIBGNUTLS
214  return gnutls_handshake(tlsConn);
215 #else
216  return sizeof(tlsConn); // the value is unused; should be unreachable
217 #endif
218  });
219 }
220 
224 {
225  return Handshake(transport, SQUID_TLS_ERR_CONNECT, [] (ConnectionPointer tlsConn) {
226 #if USE_OPENSSL
227  return SSL_connect(tlsConn);
228 #elif HAVE_LIBGNUTLS
229  return gnutls_handshake(tlsConn);
230 #else
231  return sizeof(tlsConn); // the value is unused; should be unreachable
232 #endif
233  });
234 }
235 
int ErrorCode
Squid-defined error code (<0), an error code returned by X.509 API, or zero.
Definition: forward.h:131
#define DBG_CRITICAL
Definition: Stream.h:37
void printDescription(std::ostream &) const
common part of printGist() and printWithExtras()
Definition: Io.cc:28
void printWithExtras(std::ostream &) const
Definition: Io.cc:58
Security::LockingPointer< X509, X509_free_cpp, HardFun< int, X509 *, X509_up_ref > > CertPointer
Definition: forward.h:88
std::ostream & ForceAlert(std::ostream &s)
Definition: debug.cc:1411
a summary a TLS I/O operation outcome
Definition: Io.h:19
void printGist(std::ostream &) const
reports brief summary (on one line) suitable for low-level debugging
Definition: Io.cc:49
SessionPointer::element_type * ConnectionPointer
Definition: Io.cc:22
Category category
primary outcome classification
Definition: Io.h:45
void ForgetErrors()
clear any errors that a TLS library has accumulated in its global storage
Definition: Io.cc:70
#define assert(EX)
Definition: assert.h:17
static IoResult Handshake(Comm::Connection &, ErrorCode, Fun)
static std::ostream & Extra(std::ostream &)
Definition: debug.cc:1316
#define fd_table
Definition: fde.h:189
@ SQUID_TLS_ERR_CONNECT
failure to establish a connection with a TLS server
Definition: forward.h:234
const char * errorDescription
a brief description of an error
Definition: Io.h:48
IoResult Connect(Comm::Connection &transport)
establish a TLS connection over the specified from-Squid transport connection
Definition: Io.cc:223
void ForgetErrors()
Clear any errors accumulated by OpenSSL in its global storage.
Definition: gadgets.cc:65
#define DBG_IMPORTANT
Definition: Stream.h:38
@ SQUID_TLS_ERR_ACCEPT
failure to accept a connection from a TLS client
Definition: forward.h:233
IoResult Accept(Comm::Connection &transport)
accept a TLS connection over the specified to-Squid transport connection
Definition: Io.cc:208
bool isOpen() const
Definition: Connection.h:101
interface for supplying additional information about a transaction failure
Definition: Detail.h:20
void PrepForIo()
Definition: Io.cc:78
Network/connection security abstraction layer.
Definition: Connection.h:33
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192
int ssl_ex_index_ssl_error_detail

 

Introduction

Documentation

Support

Miscellaneous