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