]>
Commit | Line | Data |
---|---|---|
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 | ||
26 | CBDATA_NAMESPACED_CLASS_INIT(Ssl, PeerConnector); | |
27 | ||
28 | Ssl::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 | ||
41 | Ssl::PeerConnector::~PeerConnector() | |
42 | { | |
43 | debugs(83, 5, "Peer connector " << this << " gone"); | |
44 | } | |
45 | ||
46 | bool Ssl::PeerConnector::doneAll() const | |
47 | { | |
48 | return (!callback || callback->canceled()) && AsyncJob::doneAll(); | |
49 | } | |
50 | ||
51 | /// Preps connection and SSL state. Calls negotiate(). | |
52 | void | |
53 | Ssl::PeerConnector::start() | |
54 | { | |
55 | AsyncJob::start(); | |
56 | ||
57 | if (prepareSocket()) { | |
58 | initializeSsl(); | |
59 | negotiateSsl(); | |
60 | } | |
61 | } | |
62 | ||
63 | void | |
64 | Ssl::PeerConnector::commCloseHandler(const CommCloseCbParams ¶ms) | |
65 | { | |
66 | debugs(83, 5, "FD " << params.fd << ", Ssl::PeerConnector=" << params.data); | |
67 | connectionClosed("Ssl::PeerConnector::commCloseHandler"); | |
68 | } | |
69 | ||
70 | void | |
71 | Ssl::PeerConnector::connectionClosed(const char *reason) | |
72 | { | |
73 | mustStop(reason); | |
74 | callback = NULL; | |
75 | } | |
76 | ||
77 | bool | |
78 | Ssl::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 | ||
93 | void | |
94 | Ssl::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 | ||
180 | void | |
181 | Ssl::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 | ||
248 | void | |
249 | Ssl::PeerConnector::sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &validationResponse) | |
250 | { | |
251 | Ssl::PeerConnector *connector = (Ssl::PeerConnector *)(data); | |
252 | connector->sslCrtvdHandleReply(validationResponse); | |
253 | } | |
254 | ||
255 | void | |
256 | Ssl::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. | |
311 | Ssl::CertErrors * | |
312 | Ssl::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. | |
364 | void | |
365 | Ssl::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 | ||
372 | void | |
373 | Ssl::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 | ||
460 | void | |
461 | Ssl::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 | ||
487 | void | |
488 | Ssl::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 | ||
505 | void | |
506 | Ssl::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 | ||
513 | const char * | |
514 | Ssl::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 | ||
536 | Ssl::PeerConnectorAnswer::~PeerConnectorAnswer() | |
537 | { | |
538 | delete error.get(); | |
539 | } | |
540 | ||
541 | std::ostream & | |
542 | operator <<(std::ostream &os, const Ssl::PeerConnectorAnswer &answer) | |
543 | { | |
544 | return os << answer.conn << ", " << answer.error; | |
545 | } |