]> git.ipfire.org Git - thirdparty/squid.git/blame - src/ssl/PeerConnector.cc
Bug 1961: pt1: URL handling redesign
[thirdparty/squid.git] / src / ssl / PeerConnector.cc
CommitLineData
a23223bf
CT
1/*
2 * DEBUG: section 17 Request Forwarding
3 *
4 */
5
6#include "squid.h"
7#include "acl/FilledChecklist.h"
8#include "base/AsyncCbdataCalls.h"
9#include "CachePeer.h"
10#include "client_side.h"
11#include "comm/Loops.h"
12#include "errorpage.h"
13#include "fde.h"
14#include "globals.h"
15#include "HttpRequest.h"
16#include "neighbors.h"
17#include "ssl/cert_validate_message.h"
18#include "ssl/Config.h"
19#include "ssl/ErrorDetail.h"
20#include "ssl/helper.h"
21#include "ssl/PeerConnector.h"
22#include "ssl/ServerBump.h"
23#include "ssl/support.h"
24#include "SquidConfig.h"
25
26CBDATA_NAMESPACED_CLASS_INIT(Ssl, PeerConnector);
27
28Ssl::PeerConnector::PeerConnector(
29 HttpRequestPointer &aRequest,
30 const Comm::ConnectionPointer &aServerConn,
31 AsyncCall::Pointer &aCallback):
32 AsyncJob("Ssl::PeerConnector"),
33 request(aRequest),
34 serverConn(aServerConn),
35 callback(aCallback)
36{
37 // if this throws, the caller's cb dialer is not our CbDialer
38 Must(dynamic_cast<CbDialer*>(callback->getDialer()));
39}
40
41Ssl::PeerConnector::~PeerConnector()
42{
43 debugs(83, 5, "Peer connector " << this << " gone");
44}
45
46bool Ssl::PeerConnector::doneAll() const
47{
48 return (!callback || callback->canceled()) && AsyncJob::doneAll();
49}
50
51/// Preps connection and SSL state. Calls negotiate().
52void
53Ssl::PeerConnector::start()
54{
55 AsyncJob::start();
56
57 if (prepareSocket()) {
58 initializeSsl();
59 negotiateSsl();
60 }
61}
62
63void
64Ssl::PeerConnector::commCloseHandler(const CommCloseCbParams &params)
65{
66 debugs(83, 5, "FD " << params.fd << ", Ssl::PeerConnector=" << params.data);
67 connectionClosed("Ssl::PeerConnector::commCloseHandler");
68}
69
70void
71Ssl::PeerConnector::connectionClosed(const char *reason)
72{
73 mustStop(reason);
74 callback = NULL;
75}
76
77bool
78Ssl::PeerConnector::prepareSocket()
79{
80 const int fd = serverConnection()->fd;
81 if (!Comm::IsConnOpen(serverConn) || fd_table[serverConn->fd].closing()) {
82 connectionClosed("Ssl::PeerConnector::prepareSocket");
83 return false;
84 }
85
86 // watch for external connection closures
87 typedef CommCbMemFunT<Ssl::PeerConnector, CommCloseCbParams> Dialer;
88 closeHandler = JobCallback(9, 5, Dialer, this, Ssl::PeerConnector::commCloseHandler);
89 comm_add_close_handler(fd, closeHandler);
90 return true;
91}
92
93void
94Ssl::PeerConnector::initializeSsl()
95{
96 SSL *ssl;
97 SSL_CTX *sslContext = NULL;
98 const CachePeer *peer = serverConnection()->getPeer();
99 const int fd = serverConnection()->fd;
100
101 if (peer) {
102 assert(peer->use_ssl);
103 sslContext = peer->sslContext;
104 } else {
105 sslContext = ::Config.ssl_client.sslContext;
106 }
107
108 assert(sslContext);
109
110 if ((ssl = SSL_new(sslContext)) == NULL) {
111 ErrorState *anErr = new ErrorState(ERR_SOCKET_FAILURE, Http::scInternalServerError, request.getRaw());
112 anErr->xerrno = errno;
113 debugs(83, DBG_IMPORTANT, "Error allocating SSL handle: " << ERR_error_string(ERR_get_error(), NULL));
114 bail(anErr);
115 return;
116 }
117
118 SSL_set_fd(ssl, fd);
119
120 if (peer) {
121 if (peer->ssldomain)
122 SSL_set_ex_data(ssl, ssl_ex_index_server, peer->ssldomain);
123
124#if NOT_YET
125
126 else if (peer->name)
127 SSL_set_ex_data(ssl, ssl_ex_index_server, peer->name);
128
129#endif
130
131 else
132 SSL_set_ex_data(ssl, ssl_ex_index_server, peer->host);
133
134 if (peer->sslSession)
135 SSL_set_session(ssl, peer->sslSession);
136
137 } else {
138 // While we are peeking at the certificate, we may not know the server
139 // name that the client will request (after interception or CONNECT)
140 // unless it was the CONNECT request with a user-typed address.
141 const char *hostname = request->GetHost();
142 const bool hostnameIsIp = request->GetHostIsNumeric();
143 const bool isConnectRequest = request->clientConnectionManager.valid() &&
144 !request->clientConnectionManager->port->flags.isIntercepted();
145 if (!request->flags.sslPeek || isConnectRequest)
146 SSL_set_ex_data(ssl, ssl_ex_index_server, (void*)hostname);
147
148 // Use SNI TLS extension only when we connect directly
149 // to the origin server and we know the server host name.
150 if (!hostnameIsIp)
151 Ssl::setClientSNI(ssl, hostname);
152 }
153
154 // If CertValidation Helper used do not lookup checklist for errors,
155 // but keep a list of errors to send it to CertValidator
156 if (!Ssl::TheConfig.ssl_crt_validator) {
157 // Create the ACL check list now, while we have access to more info.
158 // The list is used in ssl_verify_cb() and is freed in ssl_free().
159 if (acl_access *acl = ::Config.ssl_client.cert_error) {
160 ACLFilledChecklist *check = new ACLFilledChecklist(acl, request.getRaw(), dash_str);
161 // check->fd(fd); XXX: need client FD here
162 SSL_set_ex_data(ssl, ssl_ex_index_cert_error_check, check);
163 }
164 }
165
166 // store peeked cert to check SQUID_X509_V_ERR_CERT_CHANGE
167 X509 *peeked_cert;
168 if (request->clientConnectionManager.valid() &&
169 request->clientConnectionManager->serverBump() &&
170 (peeked_cert = request->clientConnectionManager->serverBump()->serverCert.get())) {
171 CRYPTO_add(&(peeked_cert->references),1,CRYPTO_LOCK_X509);
172 SSL_set_ex_data(ssl, ssl_ex_index_ssl_peeked_cert, peeked_cert);
173 }
174
175 fd_table[fd].ssl = ssl;
176 fd_table[fd].read_method = &ssl_read_method;
177 fd_table[fd].write_method = &ssl_write_method;
178}
179
180void
181Ssl::PeerConnector::negotiateSsl()
182{
183 if (!Comm::IsConnOpen(serverConnection()) || fd_table[serverConnection()->fd].closing())
184 return;
185
186 const int fd = serverConnection()->fd;
187 SSL *ssl = fd_table[fd].ssl;
188 const int result = SSL_connect(ssl);
189 if (result <= 0) {
190 handleNegotiateError(result);
191 return; // we might be gone by now
192 }
193
194 if (request->clientConnectionManager.valid()) {
195 // remember the server certificate from the ErrorDetail object
196 if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) {
197 serverBump->serverCert.reset(SSL_get_peer_certificate(ssl));
198
199 // remember validation errors, if any
200 if (Ssl::CertErrors *errs = static_cast<Ssl::CertErrors *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors)))
201 serverBump->sslErrors = cbdataReference(errs);
202 }
203 }
204
205 if (serverConnection()->getPeer() && !SSL_session_reused(ssl)) {
206 if (serverConnection()->getPeer()->sslSession)
207 SSL_SESSION_free(serverConnection()->getPeer()->sslSession);
208
209 serverConnection()->getPeer()->sslSession = SSL_get1_session(ssl);
210 }
211
212 if (Ssl::TheConfig.ssl_crt_validator) {
213 Ssl::CertValidationRequest validationRequest;
214 // WARNING: Currently we do not use any locking for any of the
215 // members of the Ssl::CertValidationRequest class. In this code the
216 // Ssl::CertValidationRequest object used only to pass data to
217 // Ssl::CertValidationHelper::submit method.
218 validationRequest.ssl = ssl;
219 validationRequest.domainName = request->GetHost();
220 if (Ssl::CertErrors *errs = static_cast<Ssl::CertErrors *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors)))
221 // validationRequest disappears on return so no need to cbdataReference
222 validationRequest.errors = errs;
223 else
224 validationRequest.errors = NULL;
225 try {
226 debugs(83, 5, "Sending SSL certificate for validation to ssl_crtvd.");
227 Ssl::CertValidationHelper::GetInstance()->sslSubmit(validationRequest, sslCrtvdHandleReplyWrapper, this);
228 return;
229 } catch (const std::exception &e) {
230 debugs(83, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtvd " <<
231 "request for " << validationRequest.domainName <<
232 " certificate: " << e.what() << "; will now block to " <<
233 "validate that certificate.");
234 // fall through to do blocking in-process generation.
235 ErrorState *anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw());
236 bail(anErr);
237 if (serverConnection()->getPeer()) {
238 peerConnectFailed(serverConnection()->getPeer());
239 }
240 serverConn->close();
241 return;
242 }
243 }
244
245 callBack();
246}
247
248void
249Ssl::PeerConnector::sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &validationResponse)
250{
251 Ssl::PeerConnector *connector = (Ssl::PeerConnector *)(data);
252 connector->sslCrtvdHandleReply(validationResponse);
253}
254
255void
256Ssl::PeerConnector::sslCrtvdHandleReply(Ssl::CertValidationResponse const &validationResponse)
257{
258 Ssl::CertErrors *errs = NULL;
259 Ssl::ErrorDetail *errDetails = NULL;
260 bool validatorFailed = false;
261 if (!Comm::IsConnOpen(serverConnection())) {
262 return;
263 }
264
265 debugs(83,5, request->GetHost() << " cert validation result: " << validationResponse.resultCode);
266
267 if (validationResponse.resultCode == HelperReply::Error)
268 errs = sslCrtvdCheckForErrors(validationResponse, errDetails);
269 else if (validationResponse.resultCode != HelperReply::Okay)
270 validatorFailed = true;
271
272 if (!errDetails && !validatorFailed) {
273 callBack();
274 return;
275 }
276
277 ErrorState *anErr = NULL;
278 if (validatorFailed) {
279 anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw());
280 } else {
281
282 // Check the list error with
283 if (errDetails && request->clientConnectionManager.valid()) {
284 // remember the server certificate from the ErrorDetail object
285 if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) {
286 // remember validation errors, if any
287 if (errs) {
288 if (serverBump->sslErrors)
289 cbdataReferenceDone(serverBump->sslErrors);
290 serverBump->sslErrors = cbdataReference(errs);
291 }
292 }
293 }
294
295 anErr = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request.getRaw());
296 anErr->detail = errDetails;
297 /*anErr->xerrno= Should preserved*/
298 }
299
300 bail(anErr);
301 if (serverConnection()->getPeer()) {
302 peerConnectFailed(serverConnection()->getPeer());
303 }
304 serverConn->close();
305 return;
306}
307
308/// Checks errors in the cert. validator response against sslproxy_cert_error.
309/// The first honored error, if any, is returned via errDetails parameter.
310/// The method returns all seen errors except SSL_ERROR_NONE as Ssl::CertErrors.
311Ssl::CertErrors *
312Ssl::PeerConnector::sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &resp, Ssl::ErrorDetail *& errDetails)
313{
314 Ssl::CertErrors *errs = NULL;
315
316 ACLFilledChecklist *check = NULL;
317 if (acl_access *acl = ::Config.ssl_client.cert_error)
318 check = new ACLFilledChecklist(acl, request.getRaw(), dash_str);
319
320 SSL *ssl = fd_table[serverConnection()->fd].ssl;
321 typedef Ssl::CertValidationResponse::RecvdErrors::const_iterator SVCRECI;
322 for (SVCRECI i = resp.errors.begin(); i != resp.errors.end(); ++i) {
323 debugs(83, 7, "Error item: " << i->error_no << " " << i->error_reason);
324
325 assert(i->error_no != SSL_ERROR_NONE);
326
327 if (!errDetails) {
328 bool allowed = false;
329 if (check) {
330 check->sslErrors = new Ssl::CertErrors(Ssl::CertError(i->error_no, i->cert.get()));
331 if (check->fastCheck() == ACCESS_ALLOWED)
332 allowed = true;
333 }
334 // else the Config.ssl_client.cert_error access list is not defined
335 // and the first error will cause the error page
336
337 if (allowed) {
338 debugs(83, 3, "bypassing SSL error " << i->error_no << " in " << "buffer");
339 } else {
340 debugs(83, 5, "confirming SSL error " << i->error_no);
341 X509 *brokenCert = i->cert.get();
342 Ssl::X509_Pointer peerCert(SSL_get_peer_certificate(ssl));
343 const char *aReason = i->error_reason.empty() ? NULL : i->error_reason.c_str();
344 errDetails = new Ssl::ErrorDetail(i->error_no, peerCert.get(), brokenCert, aReason);
345 }
346 if (check) {
347 delete check->sslErrors;
348 check->sslErrors = NULL;
349 }
350 }
351
352 if (!errs)
353 errs = new Ssl::CertErrors(Ssl::CertError(i->error_no, i->cert.get()));
354 else
355 errs->push_back_unique(Ssl::CertError(i->error_no, i->cert.get()));
356 }
357 if (check)
358 delete check;
359
360 return errs;
361}
362
363/// A wrapper for Comm::SetSelect() notifications.
364void
365Ssl::PeerConnector::NegotiateSsl(int, void *data)
366{
367 PeerConnector *pc = static_cast<PeerConnector*>(data);
368 // Use job calls to add done() checks and other job logic/protections.
369 CallJobHere(83, 7, pc, Ssl::PeerConnector, negotiateSsl);
370}
371
372void
373Ssl::PeerConnector::handleNegotiateError(const int ret)
374{
375 const int fd = serverConnection()->fd;
376 unsigned long ssl_lib_error = SSL_ERROR_NONE;
377 SSL *ssl = fd_table[fd].ssl;
378 int ssl_error = SSL_get_error(ssl, ret);
379
380#ifdef EPROTO
381 int sysErrNo = EPROTO;
382#else
383 int sysErrNo = EACCES;
384#endif
385
386 switch (ssl_error) {
387
388 case SSL_ERROR_WANT_READ:
389 Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0);
390 return;
391
392 case SSL_ERROR_WANT_WRITE:
393 Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, this, 0);
394 return;
395
396 case SSL_ERROR_SSL:
397 case SSL_ERROR_SYSCALL:
398 ssl_lib_error = ERR_get_error();
399
400 // store/report errno when ssl_error is SSL_ERROR_SYSCALL, ssl_lib_error is 0, and ret is -1
401 if (ssl_error == SSL_ERROR_SYSCALL && ret == -1 && ssl_lib_error == 0)
402 sysErrNo = errno;
403
404 debugs(83, DBG_IMPORTANT, "Error negotiating SSL on FD " << fd <<
405 ": " << ERR_error_string(ssl_lib_error, NULL) << " (" <<
406 ssl_error << "/" << ret << "/" << errno << ")");
407
408 break; // proceed to the general error handling code
409
410 default:
411 break; // no special error handling for all other errors
412 }
413
414 ErrorState *const anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request.getRaw());
415 anErr->xerrno = sysErrNo;
416
417 Ssl::ErrorDetail *errFromFailure = (Ssl::ErrorDetail *)SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail);
418 if (errFromFailure != NULL) {
419 // The errFromFailure is attached to the ssl object
420 // and will be released when ssl object destroyed.
421 // Copy errFromFailure to a new Ssl::ErrorDetail object
422 anErr->detail = new Ssl::ErrorDetail(*errFromFailure);
423 } else {
424 // server_cert can be NULL here
425 X509 *server_cert = SSL_get_peer_certificate(ssl);
426 anErr->detail = new Ssl::ErrorDetail(SQUID_ERR_SSL_HANDSHAKE, server_cert, NULL);
427 X509_free(server_cert);
428 }
429
430 if (ssl_lib_error != SSL_ERROR_NONE)
431 anErr->detail->setLibError(ssl_lib_error);
432
433 if (request->clientConnectionManager.valid()) {
434 // remember the server certificate from the ErrorDetail object
435 if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) {
436 serverBump->serverCert.resetAndLock(anErr->detail->peerCert());
437
438 // remember validation errors, if any
439 if (Ssl::CertErrors *errs = static_cast<Ssl::CertErrors*>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors)))
440 serverBump->sslErrors = cbdataReference(errs);
441 }
442
443 // For intercepted connections, set the host name to the server
444 // certificate CN. Otherwise, we just hope that CONNECT is using
445 // a user-entered address (a host name or a user-entered IP).
446 const bool isConnectRequest = !request->clientConnectionManager->port->flags.isIntercepted();
447 if (request->flags.sslPeek && !isConnectRequest) {
448 if (X509 *srvX509 = anErr->detail->peerCert()) {
449 if (const char *name = Ssl::CommonHostName(srvX509)) {
450 request->SetHost(name);
451 debugs(83, 3, HERE << "reset request host: " << name);
452 }
453 }
454 }
455 }
456
457 bail(anErr);
458}
459
460void
461Ssl::PeerConnector::bail(ErrorState *error)
462{
463 Must(error); // or the recepient will not know there was a problem
464
465 // XXX: forward.cc calls peerConnectSucceeded() after an OK TCP connect but
466 // we call peerConnectFailed() if SSL failed afterwards. Is that OK?
467 // It is not clear whether we should call peerConnectSucceeded/Failed()
468 // based on TCP results, SSL results, or both. And the code is probably not
469 // consistent in this aspect across tunnelling and forwarding modules.
470 if (CachePeer *p = serverConnection()->getPeer())
471 peerConnectFailed(p);
472
473 Must(callback != NULL);
474 CbDialer *dialer = dynamic_cast<CbDialer*>(callback->getDialer());
475 Must(dialer);
476 dialer->answer().error = error;
477
478 callBack();
479 // Our job is done. The callabck recepient will probably close the failed
480 // peer connection and try another peer or go direct (if possible). We
481 // can close the connection ourselves (our error notification would reach
482 // the recepient before the fd-closure notification), but we would rather
483 // minimize the number of fd-closure notifications and let the recepient
484 // manage the TCP state of the connection.
485}
486
487void
488Ssl::PeerConnector::callBack()
489{
490 AsyncCall::Pointer cb = callback;
491 // Do this now so that if we throw below, swanSong() assert that we _tried_
492 // to call back holds.
493 callback = NULL; // this should make done() true
494
495 // remove close handler
496 comm_remove_close_handler(serverConnection()->fd, closeHandler);
497
498 CbDialer *dialer = dynamic_cast<CbDialer*>(cb->getDialer());
499 Must(dialer);
500 dialer->answer().conn = serverConnection();
501 ScheduleCallHere(cb);
502}
503
504
505void
506Ssl::PeerConnector::swanSong()
507{
508 // XXX: unregister fd-closure monitoring and CommSetSelect interest, if any
509 AsyncJob::swanSong();
510 assert(!callback); // paranoid: we have not left the caller waiting
511}
512
513const char *
514Ssl::PeerConnector::status() const
515{
516 static MemBuf buf;
517 buf.reset();
518
519 // TODO: redesign AsyncJob::status() API to avoid this
520 // id and stop reason reporting duplication.
521 buf.append(" [", 2);
522 if (stopReason != NULL) {
523 buf.Printf("Stopped, reason:");
524 buf.Printf("%s",stopReason);
525 }
526 if (serverConn != NULL)
527 buf.Printf(" FD %d", serverConn->fd);
528 buf.Printf(" %s%u]", id.Prefix, id.value);
529 buf.terminate();
530
531 return buf.content();
532}
533
534/* PeerConnectorAnswer */
535
536Ssl::PeerConnectorAnswer::~PeerConnectorAnswer()
537{
538 delete error.get();
539}
540
541std::ostream &
542operator <<(std::ostream &os, const Ssl::PeerConnectorAnswer &answer)
543{
544 return os << answer.conn << ", " << answer.error;
545}