2 * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 93 ICAP (RFC 3507) Client */
12 #include "acl/FilledChecklist.h"
13 #include "adaptation/icap/Config.h"
14 #include "adaptation/icap/Launcher.h"
15 #include "adaptation/icap/Xaction.h"
16 #include "base/TextException.h"
18 #include "comm/Connection.h"
19 #include "comm/ConnOpener.h"
20 #include "comm/Read.h"
21 #include "comm/Write.h"
22 #include "CommCalls.h"
23 #include "err_detail_type.h"
27 #include "HttpReply.h"
31 #include "security/PeerConnector.h"
32 #include "SquidConfig.h"
33 #include "SquidTime.h"
35 /// Gives Security::PeerConnector access to Answer in the PeerPoolMgr callback dialer.
36 class MyIcapAnswerDialer
: public UnaryMemFunT
<Adaptation::Icap::Xaction
, Security::EncryptorAnswer
, Security::EncryptorAnswer
&>,
37 public Security::PeerConnector::CbDialer
40 MyIcapAnswerDialer(const JobPointer
&aJob
, Method aMethod
):
41 UnaryMemFunT
<Adaptation::Icap::Xaction
, Security::EncryptorAnswer
, Security::EncryptorAnswer
&>(aJob
, aMethod
, Security::EncryptorAnswer()) {}
43 /* Security::PeerConnector::CbDialer API */
44 virtual Security::EncryptorAnswer
&answer() { return arg1
; }
49 /// A simple PeerConnector for Secure ICAP services. No SslBump capabilities.
50 class IcapPeerConnector
: public Security::PeerConnector
{
51 CBDATA_CLASS(IcapPeerConnector
);
54 Adaptation::Icap::ServiceRep::Pointer
&service
,
55 const Comm::ConnectionPointer
&aServerConn
,
56 AsyncCall::Pointer
&aCallback
,
57 AccessLogEntry::Pointer
const &alp
,
58 const time_t timeout
= 0):
59 AsyncJob("Ssl::IcapPeerConnector"),
60 Security::PeerConnector(aServerConn
, aCallback
, alp
, timeout
), icapService(service
) {}
62 /* Security::PeerConnector API */
63 virtual bool initialize(Security::SessionPointer
&);
64 virtual void noteNegotiationDone(ErrorState
*error
);
65 virtual Security::ContextPointer
getTlsContext() {
66 return icapService
->sslContext
;
70 Adaptation::Icap::ServiceRep::Pointer icapService
;
74 CBDATA_NAMESPACED_CLASS_INIT(Ssl
, IcapPeerConnector
);
76 Adaptation::Icap::Xaction::Xaction(const char *aTypeName
, Adaptation::Icap::ServiceRep::Pointer
&aService
):
78 Adaptation::Initiate(aTypeName
),
85 reuseConnection(true),
88 ignoreLastWrite(false),
94 alep(new AccessLogEntry
),
98 debugs(93,3, typeName
<< " constructed, this=" << this <<
99 " [icapx" << id
<< ']'); // we should not call virtual status() here
100 const MasterXaction::Pointer mx
= new MasterXaction(XactionInitiator::initAdaptation
);
101 icapRequest
= new HttpRequest(mx
);
102 HTTPMSGLOCK(icapRequest
);
103 icap_tr_start
= current_time
;
104 memset(&icap_tio_start
, 0, sizeof(icap_tio_start
));
105 memset(&icap_tio_finish
, 0, sizeof(icap_tio_finish
));
108 Adaptation::Icap::Xaction::~Xaction()
110 debugs(93,3, typeName
<< " destructed, this=" << this <<
111 " [icapx" << id
<< ']'); // we should not call virtual status() here
112 HTTPMSGUNLOCK(icapRequest
);
115 AccessLogEntry::Pointer
116 Adaptation::Icap::Xaction::masterLogEntry()
118 AccessLogEntry::Pointer nil
;
122 Adaptation::Icap::ServiceRep
&
123 Adaptation::Icap::Xaction::service()
125 Must(theService
!= NULL
);
129 void Adaptation::Icap::Xaction::disableRetries()
131 debugs(93,5, typeName
<< (isRetriable
? " from now on" : " still") <<
132 " cannot be retried " << status());
136 void Adaptation::Icap::Xaction::disableRepeats(const char *reason
)
138 debugs(93,5, typeName
<< (isRepeatable
? " from now on" : " still") <<
139 " cannot be repeated because " << reason
<< status());
140 isRepeatable
= false;
143 void Adaptation::Icap::Xaction::start()
145 Adaptation::Initiate::start();
149 icapLookupDnsResults(const ipcache_addrs
*ia
, const Dns::LookupDetails
&, void *data
)
151 Adaptation::Icap::Xaction
*xa
= static_cast<Adaptation::Icap::Xaction
*>(data
);
152 xa
->dnsLookupDone(ia
);
155 // TODO: obey service-specific, OPTIONS-reported connection limit
157 Adaptation::Icap::Xaction::openConnection()
159 Must(!haveConnection());
161 Adaptation::Icap::ServiceRep
&s
= service();
163 if (!TheConfig
.reuse_connections
)
164 disableRetries(); // this will also safely drain pconn pool
166 bool wasReused
= false;
167 connection
= s
.getConnection(isRetriable
, wasReused
);
169 if (wasReused
&& Comm::IsConnOpen(connection
)) {
170 // Set comm Close handler
171 // fake the connect callback
172 // TODO: can we sync call Adaptation::Icap::Xaction::noteCommConnected here instead?
173 typedef CommCbMemFunT
<Adaptation::Icap::Xaction
, CommConnectCbParams
> Dialer
;
174 CbcPointer
<Xaction
> self(this);
175 Dialer
dialer(self
, &Adaptation::Icap::Xaction::noteCommConnected
);
176 dialer
.params
.conn
= connection
;
177 dialer
.params
.flag
= Comm::OK
;
178 // fake other parameters by copying from the existing connection
179 connector
= asyncCall(93,3, "Adaptation::Icap::Xaction::noteCommConnected", dialer
);
180 ScheduleCallHere(connector
);
184 disableRetries(); // we only retry pconn failures
186 // Attempt to open a new connection...
187 debugs(93,3, typeName
<< " opens connection to " << s
.cfg().host
.termedBuf() << ":" << s
.cfg().port
);
189 // Locate the Service IP(s) to open
190 ipcache_nbgethostbyname(s
.cfg().host
.termedBuf(), icapLookupDnsResults
, this);
194 Adaptation::Icap::Xaction::dnsLookupDone(const ipcache_addrs
*ia
)
196 Adaptation::Icap::ServiceRep
&s
= service();
199 debugs(44, DBG_IMPORTANT
, "ICAP: Unknown service host: " << s
.cfg().host
);
201 #if WHEN_IPCACHE_NBGETHOSTBYNAME_USES_ASYNC_CALLS
202 dieOnConnectionFailure(); // throws
203 #else // take a step back into protected Async call dialing.
204 // fake the connect callback
205 typedef CommCbMemFunT
<Adaptation::Icap::Xaction
, CommConnectCbParams
> Dialer
;
206 CbcPointer
<Xaction
> self(this);
207 Dialer
dialer(self
, &Adaptation::Icap::Xaction::noteCommConnected
);
208 dialer
.params
.conn
= connection
;
209 dialer
.params
.flag
= Comm::COMM_ERROR
;
210 // fake other parameters by copying from the existing connection
211 connector
= asyncCall(93,3, "Adaptation::Icap::Xaction::noteCommConnected", dialer
);
212 ScheduleCallHere(connector
);
217 assert(ia
->cur
< ia
->count
);
219 connection
= new Comm::Connection
;
220 connection
->remote
= ia
->in_addrs
[ia
->cur
];
221 connection
->remote
.port(s
.cfg().port
);
222 getOutgoingAddress(NULL
, connection
);
224 // TODO: service bypass status may differ from that of a transaction
225 typedef CommCbMemFunT
<Adaptation::Icap::Xaction
, CommConnectCbParams
> ConnectDialer
;
226 connector
= JobCallback(93,3, ConnectDialer
, this, Adaptation::Icap::Xaction::noteCommConnected
);
227 cs
= new Comm::ConnOpener(connection
, connector
, TheConfig
.connect_timeout(service().cfg().bypass
));
228 cs
->setHost(s
.cfg().host
.termedBuf());
229 AsyncJob::Start(cs
.get());
233 * This event handler is necessary to work around the no-rentry policy
234 * of Adaptation::Icap::Xaction::callStart()
238 Adaptation::Icap::Xaction::reusedConnection(void *data
)
240 debugs(93, 5, HERE
<< "reused connection");
241 Adaptation::Icap::Xaction
*x
= (Adaptation::Icap::Xaction
*)data
;
242 x
->noteCommConnected(Comm::OK
);
246 void Adaptation::Icap::Xaction::closeConnection()
248 if (haveConnection()) {
250 if (closer
!= NULL
) {
251 comm_remove_close_handler(connection
->fd
, closer
);
255 cancelRead(); // may not work
257 if (reuseConnection
&& !doneWithIo()) {
258 //status() adds leading spaces.
259 debugs(93,5, HERE
<< "not reusing pconn due to pending I/O" << status());
260 reuseConnection
= false;
266 const bool reset
= !reuseConnection
&&
267 (al
.icap
.outcome
== xoGone
|| al
.icap
.outcome
== xoError
);
269 Adaptation::Icap::ServiceRep
&s
= service();
270 s
.putConnection(connection
, reuseConnection
, reset
, status());
279 // connection with the ICAP service established
280 void Adaptation::Icap::Xaction::noteCommConnected(const CommConnectCbParams
&io
)
284 if (io
.flag
== Comm::TIMEOUT
) {
285 handleCommTimedout();
289 Must(connector
!= NULL
);
292 if (io
.flag
!= Comm::OK
)
293 dieOnConnectionFailure(); // throws
295 typedef CommCbMemFunT
<Adaptation::Icap::Xaction
, CommTimeoutCbParams
> TimeoutDialer
;
296 AsyncCall::Pointer timeoutCall
= asyncCall(93, 5, "Adaptation::Icap::Xaction::noteCommTimedout",
297 TimeoutDialer(this,&Adaptation::Icap::Xaction::noteCommTimedout
));
298 commSetConnTimeout(io
.conn
, TheConfig
.connect_timeout(service().cfg().bypass
), timeoutCall
);
300 typedef CommCbMemFunT
<Adaptation::Icap::Xaction
, CommCloseCbParams
> CloseDialer
;
301 closer
= asyncCall(93, 5, "Adaptation::Icap::Xaction::noteCommClosed",
302 CloseDialer(this,&Adaptation::Icap::Xaction::noteCommClosed
));
303 comm_add_close_handler(io
.conn
->fd
, closer
);
305 // If it is a reused connection and the TLS object is built
306 // we should not negotiate new TLS session
307 const auto &ssl
= fd_table
[io
.conn
->fd
].ssl
;
308 if (!ssl
&& service().cfg().secure
.encryptTransport
) {
309 CbcPointer
<Adaptation::Icap::Xaction
> me(this);
310 securer
= asyncCall(93, 4, "Adaptation::Icap::Xaction::handleSecuredPeer",
311 MyIcapAnswerDialer(me
, &Adaptation::Icap::Xaction::handleSecuredPeer
));
313 auto *sslConnector
= new Ssl::IcapPeerConnector(theService
, io
.conn
, securer
, masterLogEntry(), TheConfig
.connect_timeout(service().cfg().bypass
));
314 AsyncJob::Start(sslConnector
); // will call our callback
318 // ?? fd_table[io.conn->fd].noteUse(icapPconnPool);
319 service().noteConnectionUse(connection
);
321 handleCommConnected();
324 void Adaptation::Icap::Xaction::dieOnConnectionFailure()
326 debugs(93, 2, HERE
<< typeName
<<
327 " failed to connect to " << service().cfg().uri
);
328 service().noteConnectionFailed("failure");
329 detailError(ERR_DETAIL_ICAP_XACT_START
);
330 throw TexcHere("cannot connect to the ICAP service");
333 void Adaptation::Icap::Xaction::scheduleWrite(MemBuf
&buf
)
335 Must(haveConnection());
337 // comm module will free the buffer
338 typedef CommCbMemFunT
<Adaptation::Icap::Xaction
, CommIoCbParams
> Dialer
;
339 writer
= JobCallback(93, 3,
340 Dialer
, this, Adaptation::Icap::Xaction::noteCommWrote
);
342 Comm::Write(connection
, &buf
, writer
);
346 void Adaptation::Icap::Xaction::noteCommWrote(const CommIoCbParams
&io
)
348 Must(writer
!= NULL
);
351 if (ignoreLastWrite
) {
352 // a hack due to comm inability to cancel a pending write
353 ignoreLastWrite
= false;
354 debugs(93, 7, HERE
<< "ignoring last write; status: " << io
.flag
);
356 Must(io
.flag
== Comm::OK
);
357 al
.icap
.bytesSent
+= io
.size
;
359 handleCommWrote(io
.size
);
363 // communication timeout with the ICAP service
364 void Adaptation::Icap::Xaction::noteCommTimedout(const CommTimeoutCbParams
&)
366 handleCommTimedout();
369 void Adaptation::Icap::Xaction::handleCommTimedout()
371 debugs(93, 2, HERE
<< typeName
<< " failed: timeout with " <<
372 theService
->cfg().methodStr() << " " <<
373 theService
->cfg().uri
<< status());
374 reuseConnection
= false;
375 const bool whileConnecting
= connector
!= NULL
;
376 if (whileConnecting
) {
377 assert(!haveConnection());
378 theService
->noteConnectionFailed("timedout");
380 closeConnection(); // so that late Comm callbacks do not disturb bypass
381 throw TexcHere(whileConnecting
?
382 "timed out while connecting to the ICAP service" :
383 "timed out while talking to the ICAP service");
386 // unexpected connection close while talking to the ICAP service
387 void Adaptation::Icap::Xaction::noteCommClosed(const CommCloseCbParams
&)
389 if (securer
!= NULL
) {
390 securer
->cancel("Connection closed before SSL negotiation finished");
397 void Adaptation::Icap::Xaction::handleCommClosed()
399 detailError(ERR_DETAIL_ICAP_XACT_CLOSE
);
400 mustStop("ICAP service connection externally closed");
403 void Adaptation::Icap::Xaction::callException(const std::exception
&e
)
406 service().noteFailure();
407 Adaptation::Initiate::callException(e
);
410 void Adaptation::Icap::Xaction::callEnd()
413 debugs(93, 5, HERE
<< typeName
<< " done with I/O" << status());
416 Adaptation::Initiate::callEnd(); // may destroy us
419 bool Adaptation::Icap::Xaction::doneAll() const
421 return !connector
&& !securer
&& !reader
&& !writer
&& Adaptation::Initiate::doneAll();
424 void Adaptation::Icap::Xaction::updateTimeout()
426 Must(haveConnection());
428 if (reader
!= NULL
|| writer
!= NULL
) {
429 // restart the timeout before each I/O
430 // XXX: why does Config.Timeout lacks a write timeout?
431 // TODO: service bypass status may differ from that of a transaction
432 typedef CommCbMemFunT
<Adaptation::Icap::Xaction
, CommTimeoutCbParams
> TimeoutDialer
;
433 AsyncCall::Pointer call
= JobCallback(93, 5, TimeoutDialer
, this, Adaptation::Icap::Xaction::noteCommTimedout
);
434 commSetConnTimeout(connection
, TheConfig
.io_timeout(service().cfg().bypass
), call
);
436 // clear timeout when there is no I/O
437 // Do we need a lifetime timeout?
438 commUnsetConnTimeout(connection
);
442 void Adaptation::Icap::Xaction::scheduleRead()
444 Must(haveConnection());
446 Must(readBuf
.length() < SQUID_TCP_SO_RCVBUF
); // will expand later if needed
448 typedef CommCbMemFunT
<Adaptation::Icap::Xaction
, CommIoCbParams
> Dialer
;
449 reader
= JobCallback(93, 3, Dialer
, this, Adaptation::Icap::Xaction::noteCommRead
);
450 Comm::Read(connection
, reader
);
454 // comm module read a portion of the ICAP response for us
455 void Adaptation::Icap::Xaction::noteCommRead(const CommIoCbParams
&io
)
457 Must(reader
!= NULL
);
460 Must(io
.flag
== Comm::OK
);
462 // TODO: tune this better to expected message sizes
463 readBuf
.reserveCapacity(SQUID_TCP_SO_RCVBUF
);
464 // we are not asked to grow beyond the allowed maximum
465 Must(readBuf
.length() < SQUID_TCP_SO_RCVBUF
);
466 // now we can ensure that there is space to read new data,
467 // even if readBuf.spaceSize() currently returns zero.
470 CommIoCbParams
rd(this); // will be expanded with ReadNow results
473 switch (Comm::ReadNow(rd
, readBuf
)) {
474 case Comm::INPROGRESS
:
475 if (readBuf
.isEmpty())
476 debugs(33, 2, io
.conn
<< ": no data to process, " << xstrerr(rd
.xerrno
));
481 al
.icap
.bytesRead
+= rd
.size
;
485 debugs(93, 3, "read " << rd
.size
<< " bytes");
487 disableRetries(); // because pconn did not fail
489 /* Continue to process previously read data */
492 case Comm::ENDFILE
: // close detected by 0-byte read
494 reuseConnection
= false;
496 // detect a pconn race condition: eof on the first pconn read
497 if (!al
.icap
.bytesRead
&& retriable()) {
499 mustStop("pconn race");
505 // case Comm::COMM_ERROR:
506 default: // no other flags should ever occur
507 debugs(11, 2, io
.conn
<< ": read failure: " << xstrerr(rd
.xerrno
));
508 mustStop("unknown ICAP I/O read error");
512 handleCommRead(io
.size
);
515 void Adaptation::Icap::Xaction::cancelRead()
517 if (reader
!= NULL
) {
518 Must(haveConnection());
519 Comm::ReadCancel(connection
->fd
, reader
);
525 Adaptation::Icap::Xaction::parseHttpMsg(Http::Message
*msg
)
527 debugs(93, 5, "have " << readBuf
.length() << " head bytes to parse");
529 Http::StatusCode error
= Http::scNone
;
530 // XXX: performance regression c_str() data copies
531 const char *buf
= readBuf
.c_str();
532 const bool parsed
= msg
->parse(buf
, readBuf
.length(), commEof
, &error
);
533 Must(parsed
|| !error
); // success or need more data
535 if (!parsed
) { // need more data
541 readBuf
.consume(msg
->hdr_sz
);
545 bool Adaptation::Icap::Xaction::mayReadMore() const
547 return !doneReading() && // will read more data
548 readBuf
.length() < SQUID_TCP_SO_RCVBUF
; // have space for more data
551 bool Adaptation::Icap::Xaction::doneReading() const
556 bool Adaptation::Icap::Xaction::doneWriting() const
561 bool Adaptation::Icap::Xaction::doneWithIo() const
563 return haveConnection() &&
564 !connector
&& !reader
&& !writer
&& // fast checks, some redundant
565 doneReading() && doneWriting();
568 bool Adaptation::Icap::Xaction::haveConnection() const
570 return connection
!= NULL
&& connection
->isOpen();
574 void Adaptation::Icap::Xaction::noteInitiatorAborted()
577 if (theInitiator
.set()) {
578 debugs(93,4, HERE
<< "Initiator gone before ICAP transaction ended");
580 detailError(ERR_DETAIL_ICAP_INIT_GONE
);
582 mustStop("initiator aborted");
587 void Adaptation::Icap::Xaction::setOutcome(const Adaptation::Icap::XactOutcome
&xo
)
589 if (al
.icap
.outcome
!= xoUnknown
) {
590 debugs(93, 3, HERE
<< "Warning: reseting outcome: from " <<
591 al
.icap
.outcome
<< " to " << xo
);
593 debugs(93, 4, HERE
<< xo
);
595 al
.icap
.outcome
= xo
;
598 // This 'last chance' method is called before a 'done' transaction is deleted.
599 // It is wrong to call virtual methods from a destructor. Besides, this call
600 // indicates that the transaction will terminate as planned.
601 void Adaptation::Icap::Xaction::swanSong()
603 // kids should sing first and then call the parent method.
605 debugs(93,6, HERE
<< id
<< " about to notify ConnOpener!");
606 CallJobHere(93, 3, cs
, Comm::ConnOpener
, noteAbort
);
608 service().noteConnectionFailed("abort");
611 closeConnection(); // TODO: rename because we do not always close
619 Adaptation::Initiate::swanSong();
622 void Adaptation::Icap::Xaction::tellQueryAborted()
624 if (theInitiator
.set()) {
625 Adaptation::Icap::XactAbortInfo
abortInfo(icapRequest
, icapReply
.getRaw(),
626 retriable(), repeatable());
627 Launcher
*launcher
= dynamic_cast<Launcher
*>(theInitiator
.get());
628 // launcher may be nil if initiator is invalid
629 CallJobHere1(91,5, CbcPointer
<Launcher
>(launcher
),
630 Launcher
, noteXactAbort
, abortInfo
);
635 void Adaptation::Icap::Xaction::maybeLog()
637 if (IcapLogfileStatus
== LOG_ENABLE
) {
643 void Adaptation::Icap::Xaction::finalizeLogInfo()
646 al
.icp
.opcode
= ICP_INVALID
;
648 const Adaptation::Icap::ServiceRep
&s
= service();
649 al
.icap
.hostAddr
= s
.cfg().host
.termedBuf();
650 al
.icap
.serviceName
= s
.cfg().key
;
651 al
.icap
.reqUri
= s
.cfg().uri
;
653 tvSub(al
.icap
.ioTime
, icap_tio_start
, icap_tio_finish
);
654 tvSub(al
.icap
.trTime
, icap_tr_start
, current_time
);
656 al
.icap
.request
= icapRequest
;
657 HTTPMSGLOCK(al
.icap
.request
);
658 if (icapReply
!= NULL
) {
659 al
.icap
.reply
= icapReply
.getRaw();
660 HTTPMSGLOCK(al
.icap
.reply
);
661 al
.icap
.resStatus
= icapReply
->sline
.status();
665 // returns a temporary string depicting transaction status, for debugging
666 const char *Adaptation::Icap::Xaction::status() const
671 fillPendingStatus(buf
);
674 buf
.appendf(" %s%u]", id
.prefix(), id
.value
);
677 return buf
.content();
680 void Adaptation::Icap::Xaction::fillPendingStatus(MemBuf
&buf
) const
682 if (haveConnection()) {
683 buf
.appendf("FD %d", connection
->fd
);
695 void Adaptation::Icap::Xaction::fillDoneStatus(MemBuf
&buf
) const
697 if (haveConnection() && commEof
)
698 buf
.appendf("Comm(%d)", connection
->fd
);
700 if (stopReason
!= NULL
)
701 buf
.append("Stopped", 7);
704 bool Adaptation::Icap::Xaction::fillVirginHttpHeader(MemBuf
&) const
710 Ssl::IcapPeerConnector::initialize(Security::SessionPointer
&serverSession
)
712 if (!Security::PeerConnector::initialize(serverSession
))
715 assert(!icapService
->cfg().secure
.sslDomain
.isEmpty());
717 SBuf
*host
= new SBuf(icapService
->cfg().secure
.sslDomain
);
718 SSL_set_ex_data(serverSession
.get(), ssl_ex_index_server
, host
);
720 ACLFilledChecklist
*check
= static_cast<ACLFilledChecklist
*>(SSL_get_ex_data(serverSession
.get(), ssl_ex_index_cert_error_check
));
722 check
->dst_peer_name
= *host
;
725 Security::SetSessionResumeData(serverSession
, icapService
->sslSession
);
730 Ssl::IcapPeerConnector::noteNegotiationDone(ErrorState
*error
)
735 const int fd
= serverConnection()->fd
;
736 Security::MaybeGetSessionResumeData(fd_table
[fd
].ssl
, icapService
->sslSession
);
740 Adaptation::Icap::Xaction::handleSecuredPeer(Security::EncryptorAnswer
&answer
)
742 Must(securer
!= NULL
);
745 if (closer
!= NULL
) {
746 if (answer
.conn
!= NULL
)
747 comm_remove_close_handler(answer
.conn
->fd
, closer
);
749 closer
->cancel("securing completed");
753 if (answer
.error
.get()) {
754 if (answer
.conn
!= NULL
)
755 answer
.conn
->close();
756 debugs(93, 2, typeName
<<
757 " TLS negotiation to " << service().cfg().uri
<< " failed");
758 service().noteConnectionFailed("failure");
759 detailError(ERR_DETAIL_ICAP_XACT_SSL_START
);
760 throw TexcHere("cannot connect to the TLS ICAP service");
763 debugs(93, 5, "TLS negotiation to " << service().cfg().uri
<< " complete");
765 service().noteConnectionUse(answer
.conn
);
767 handleCommConnected();