1 /*
2  * Copyright (C) 1996-2021 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 93    ICAP (RFC 3507) Client */
10 
11 #include "squid.h"
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"
17 #include "comm.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"
24 #include "fde.h"
25 #include "FwdState.h"
26 #include "globals.h"
27 #include "HttpMsg.h"
28 #include "HttpReply.h"
29 #include "HttpRequest.h"
30 #include "icap_log.h"
31 #include "ipcache.h"
32 #include "pconn.h"
33 #include "security/PeerConnector.h"
34 #include "SquidConfig.h"
35 #include "SquidTime.h"
36 
37 /// Gives Security::PeerConnector access to Answer in the PeerPoolMgr callback dialer.
38 class MyIcapAnswerDialer: public UnaryMemFunT<Adaptation::Icap::Xaction, Security::EncryptorAnswer, Security::EncryptorAnswer&>,
39     public Security::PeerConnector::CbDialer
40 {
41 public:
MyIcapAnswerDialer(const JobPointer & aJob,Method aMethod)42     MyIcapAnswerDialer(const JobPointer &aJob, Method aMethod):
43         UnaryMemFunT<Adaptation::Icap::Xaction, Security::EncryptorAnswer, Security::EncryptorAnswer&>(aJob, aMethod, Security::EncryptorAnswer()) {}
44 
45     /* Security::PeerConnector::CbDialer API */
answer()46     virtual Security::EncryptorAnswer &answer() { return arg1; }
47 };
48 
49 namespace Ssl
50 {
51 /// A simple PeerConnector for Secure ICAP services. No SslBump capabilities.
52 class IcapPeerConnector: public Security::PeerConnector {
53     CBDATA_CLASS(IcapPeerConnector);
54 public:
IcapPeerConnector(Adaptation::Icap::ServiceRep::Pointer & service,const Comm::ConnectionPointer & aServerConn,AsyncCall::Pointer & aCallback,AccessLogEntry::Pointer const & alp,const time_t timeout=0)55     IcapPeerConnector(
56         Adaptation::Icap::ServiceRep::Pointer &service,
57         const Comm::ConnectionPointer &aServerConn,
58         AsyncCall::Pointer &aCallback,
59         AccessLogEntry::Pointer const &alp,
60         const time_t timeout = 0):
61         AsyncJob("Ssl::IcapPeerConnector"),
62         Security::PeerConnector(aServerConn, aCallback, alp, timeout), icapService(service) {}
63 
64     /* Security::PeerConnector API */
65     virtual bool initialize(Security::SessionPointer &);
66     virtual void noteNegotiationDone(ErrorState *error);
getTlsContext()67     virtual Security::ContextPointer getTlsContext() {
68         return icapService->sslContext;
69     }
70 
71 private:
72     Adaptation::Icap::ServiceRep::Pointer icapService;
73 };
74 } // namespace Ssl
75 
76 CBDATA_NAMESPACED_CLASS_INIT(Ssl, IcapPeerConnector);
77 
Xaction(const char * aTypeName,Adaptation::Icap::ServiceRep::Pointer & aService)78 Adaptation::Icap::Xaction::Xaction(const char *aTypeName, Adaptation::Icap::ServiceRep::Pointer &aService):
79     AsyncJob(aTypeName),
80     Adaptation::Initiate(aTypeName),
81     icapRequest(NULL),
82     icapReply(NULL),
83     attempts(0),
84     connection(NULL),
85     theService(aService),
86     commEof(false),
87     reuseConnection(true),
88     isRetriable(true),
89     isRepeatable(true),
90     ignoreLastWrite(false),
91     stopReason(NULL),
92     connector(NULL),
93     reader(NULL),
94     writer(NULL),
95     closer(NULL),
96     alep(new AccessLogEntry),
97     al(*alep),
98     cs(NULL)
99 {
100     debugs(93,3, typeName << " constructed, this=" << this <<
101            " [icapx" << id << ']'); // we should not call virtual status() here
102     const MasterXaction::Pointer mx = new MasterXaction(XactionInitiator::initAdaptation);
103     icapRequest = new HttpRequest(mx);
104     HTTPMSGLOCK(icapRequest);
105     icap_tr_start = current_time;
106     memset(&icap_tio_start, 0, sizeof(icap_tio_start));
107     memset(&icap_tio_finish, 0, sizeof(icap_tio_finish));
108 }
109 
~Xaction()110 Adaptation::Icap::Xaction::~Xaction()
111 {
112     debugs(93,3, typeName << " destructed, this=" << this <<
113            " [icapx" << id << ']'); // we should not call virtual status() here
114     HTTPMSGUNLOCK(icapRequest);
115 }
116 
117 AccessLogEntry::Pointer
masterLogEntry()118 Adaptation::Icap::Xaction::masterLogEntry()
119 {
120     AccessLogEntry::Pointer nil;
121     return nil;
122 }
123 
124 Adaptation::Icap::ServiceRep &
service()125 Adaptation::Icap::Xaction::service()
126 {
127     Must(theService != NULL);
128     return *theService;
129 }
130 
disableRetries()131 void Adaptation::Icap::Xaction::disableRetries()
132 {
133     debugs(93,5, typeName << (isRetriable ? " from now on" : " still") <<
134            " cannot be retried " << status());
135     isRetriable = false;
136 }
137 
disableRepeats(const char * reason)138 void Adaptation::Icap::Xaction::disableRepeats(const char *reason)
139 {
140     debugs(93,5, typeName << (isRepeatable ? " from now on" : " still") <<
141            " cannot be repeated because " << reason << status());
142     isRepeatable = false;
143 }
144 
start()145 void Adaptation::Icap::Xaction::start()
146 {
147     Adaptation::Initiate::start();
148 }
149 
150 static void
icapLookupDnsResults(const ipcache_addrs * ia,const Dns::LookupDetails &,void * data)151 icapLookupDnsResults(const ipcache_addrs *ia, const Dns::LookupDetails &, void *data)
152 {
153     Adaptation::Icap::Xaction *xa = static_cast<Adaptation::Icap::Xaction *>(data);
154     xa->dnsLookupDone(ia);
155 }
156 
157 // TODO: obey service-specific, OPTIONS-reported connection limit
158 void
openConnection()159 Adaptation::Icap::Xaction::openConnection()
160 {
161     Must(!haveConnection());
162 
163     Adaptation::Icap::ServiceRep &s = service();
164 
165     if (!TheConfig.reuse_connections)
166         disableRetries(); // this will also safely drain pconn pool
167 
168     bool wasReused = false;
169     connection = s.getConnection(isRetriable, wasReused);
170 
171     if (wasReused && Comm::IsConnOpen(connection)) {
172         // Set comm Close handler
173         // fake the connect callback
174         // TODO: can we sync call Adaptation::Icap::Xaction::noteCommConnected here instead?
175         typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommConnectCbParams> Dialer;
176         CbcPointer<Xaction> self(this);
177         Dialer dialer(self, &Adaptation::Icap::Xaction::noteCommConnected);
178         dialer.params.conn = connection;
179         dialer.params.flag = Comm::OK;
180         // fake other parameters by copying from the existing connection
181         connector = asyncCall(93,3, "Adaptation::Icap::Xaction::noteCommConnected", dialer);
182         ScheduleCallHere(connector);
183         return;
184     }
185 
186     disableRetries(); // we only retry pconn failures
187 
188     // Attempt to open a new connection...
189     debugs(93,3, typeName << " opens connection to " << s.cfg().host.termedBuf() << ":" << s.cfg().port);
190 
191     // Locate the Service IP(s) to open
192     ipcache_nbgethostbyname(s.cfg().host.termedBuf(), icapLookupDnsResults, this);
193 }
194 
195 void
dnsLookupDone(const ipcache_addrs * ia)196 Adaptation::Icap::Xaction::dnsLookupDone(const ipcache_addrs *ia)
197 {
198     Adaptation::Icap::ServiceRep &s = service();
199 
200     if (ia == NULL) {
201         debugs(44, DBG_IMPORTANT, "ICAP: Unknown service host: " << s.cfg().host);
202 
203 #if WHEN_IPCACHE_NBGETHOSTBYNAME_USES_ASYNC_CALLS
204         dieOnConnectionFailure(); // throws
205 #else // take a step back into protected Async call dialing.
206         // fake the connect callback
207         typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommConnectCbParams> Dialer;
208         CbcPointer<Xaction> self(this);
209         Dialer dialer(self, &Adaptation::Icap::Xaction::noteCommConnected);
210         dialer.params.conn = connection;
211         dialer.params.flag = Comm::COMM_ERROR;
212         // fake other parameters by copying from the existing connection
213         connector = asyncCall(93,3, "Adaptation::Icap::Xaction::noteCommConnected", dialer);
214         ScheduleCallHere(connector);
215 #endif
216         return;
217     }
218 
219     assert(ia->cur < ia->count);
220 
221     connection = new Comm::Connection;
222     connection->remote = ia->in_addrs[ia->cur];
223     connection->remote.port(s.cfg().port);
224     getOutgoingAddress(NULL, connection);
225 
226     // TODO: service bypass status may differ from that of a transaction
227     typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommConnectCbParams> ConnectDialer;
228     connector = JobCallback(93,3, ConnectDialer, this, Adaptation::Icap::Xaction::noteCommConnected);
229     cs = new Comm::ConnOpener(connection, connector, TheConfig.connect_timeout(service().cfg().bypass));
230     cs->setHost(s.cfg().host.termedBuf());
231     AsyncJob::Start(cs.get());
232 }
233 
234 /*
235  * This event handler is necessary to work around the no-rentry policy
236  * of Adaptation::Icap::Xaction::callStart()
237  */
238 #if 0
239 void
240 Adaptation::Icap::Xaction::reusedConnection(void *data)
241 {
242     debugs(93, 5, HERE << "reused connection");
243     Adaptation::Icap::Xaction *x = (Adaptation::Icap::Xaction*)data;
244     x->noteCommConnected(Comm::OK);
245 }
246 #endif
247 
closeConnection()248 void Adaptation::Icap::Xaction::closeConnection()
249 {
250     if (haveConnection()) {
251 
252         if (closer != NULL) {
253             comm_remove_close_handler(connection->fd, closer);
254             closer = NULL;
255         }
256 
257         cancelRead(); // may not work
258 
259         if (reuseConnection && !doneWithIo()) {
260             //status() adds leading spaces.
261             debugs(93,5, HERE << "not reusing pconn due to pending I/O" << status());
262             reuseConnection = false;
263         }
264 
265         if (reuseConnection)
266             disableRetries();
267 
268         const bool reset = !reuseConnection &&
269                            (al.icap.outcome == xoGone || al.icap.outcome == xoError);
270 
271         Adaptation::Icap::ServiceRep &s = service();
272         s.putConnection(connection, reuseConnection, reset, status());
273 
274         writer = NULL;
275         reader = NULL;
276         connector = NULL;
277         connection = NULL;
278     }
279 }
280 
281 // connection with the ICAP service established
noteCommConnected(const CommConnectCbParams & io)282 void Adaptation::Icap::Xaction::noteCommConnected(const CommConnectCbParams &io)
283 {
284     cs = NULL;
285 
286     if (io.flag == Comm::TIMEOUT) {
287         handleCommTimedout();
288         return;
289     }
290 
291     Must(connector != NULL);
292     connector = NULL;
293 
294     if (io.flag != Comm::OK)
295         dieOnConnectionFailure(); // throws
296 
297     typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommTimeoutCbParams> TimeoutDialer;
298     AsyncCall::Pointer timeoutCall =  asyncCall(93, 5, "Adaptation::Icap::Xaction::noteCommTimedout",
299                                       TimeoutDialer(this,&Adaptation::Icap::Xaction::noteCommTimedout));
300     commSetConnTimeout(io.conn, TheConfig.connect_timeout(service().cfg().bypass), timeoutCall);
301 
302     typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommCloseCbParams> CloseDialer;
303     closer =  asyncCall(93, 5, "Adaptation::Icap::Xaction::noteCommClosed",
304                         CloseDialer(this,&Adaptation::Icap::Xaction::noteCommClosed));
305     comm_add_close_handler(io.conn->fd, closer);
306 
307     // If it is a reused connection and the TLS object is built
308     // we should not negotiate new TLS session
309     const auto &ssl = fd_table[io.conn->fd].ssl;
310     if (!ssl && service().cfg().secure.encryptTransport) {
311         CbcPointer<Adaptation::Icap::Xaction> me(this);
312         securer = asyncCall(93, 4, "Adaptation::Icap::Xaction::handleSecuredPeer",
313                             MyIcapAnswerDialer(me, &Adaptation::Icap::Xaction::handleSecuredPeer));
314 
315         auto *sslConnector = new Ssl::IcapPeerConnector(theService, io.conn, securer, masterLogEntry(), TheConfig.connect_timeout(service().cfg().bypass));
316         AsyncJob::Start(sslConnector); // will call our callback
317         return;
318     }
319 
320 // ??    fd_table[io.conn->fd].noteUse(icapPconnPool);
321     service().noteConnectionUse(connection);
322 
323     handleCommConnected();
324 }
325 
dieOnConnectionFailure()326 void Adaptation::Icap::Xaction::dieOnConnectionFailure()
327 {
328     debugs(93, 2, HERE << typeName <<
329            " failed to connect to " << service().cfg().uri);
330     service().noteConnectionFailed("failure");
331     detailError(ERR_DETAIL_ICAP_XACT_START);
332     throw TexcHere("cannot connect to the ICAP service");
333 }
334 
scheduleWrite(MemBuf & buf)335 void Adaptation::Icap::Xaction::scheduleWrite(MemBuf &buf)
336 {
337     Must(haveConnection());
338 
339     // comm module will free the buffer
340     typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommIoCbParams> Dialer;
341     writer = JobCallback(93, 3,
342                          Dialer, this, Adaptation::Icap::Xaction::noteCommWrote);
343 
344     Comm::Write(connection, &buf, writer);
345     updateTimeout();
346 }
347 
noteCommWrote(const CommIoCbParams & io)348 void Adaptation::Icap::Xaction::noteCommWrote(const CommIoCbParams &io)
349 {
350     Must(writer != NULL);
351     writer = NULL;
352 
353     if (ignoreLastWrite) {
354         // a hack due to comm inability to cancel a pending write
355         ignoreLastWrite = false;
356         debugs(93, 7, HERE << "ignoring last write; status: " << io.flag);
357     } else {
358         Must(io.flag == Comm::OK);
359         al.icap.bytesSent += io.size;
360         updateTimeout();
361         handleCommWrote(io.size);
362     }
363 }
364 
365 // communication timeout with the ICAP service
noteCommTimedout(const CommTimeoutCbParams &)366 void Adaptation::Icap::Xaction::noteCommTimedout(const CommTimeoutCbParams &)
367 {
368     handleCommTimedout();
369 }
370 
handleCommTimedout()371 void Adaptation::Icap::Xaction::handleCommTimedout()
372 {
373     debugs(93, 2, HERE << typeName << " failed: timeout with " <<
374            theService->cfg().methodStr() << " " <<
375            theService->cfg().uri << status());
376     reuseConnection = false;
377     const bool whileConnecting = connector != NULL;
378     if (whileConnecting) {
379         assert(!haveConnection());
380         theService->noteConnectionFailed("timedout");
381     } else
382         closeConnection(); // so that late Comm callbacks do not disturb bypass
383     throw TexcHere(whileConnecting ?
384                    "timed out while connecting to the ICAP service" :
385                    "timed out while talking to the ICAP service");
386 }
387 
388 // unexpected connection close while talking to the ICAP service
noteCommClosed(const CommCloseCbParams &)389 void Adaptation::Icap::Xaction::noteCommClosed(const CommCloseCbParams &)
390 {
391     if (securer != NULL) {
392         securer->cancel("Connection closed before SSL negotiation finished");
393         securer = NULL;
394     }
395     closer = NULL;
396     handleCommClosed();
397 }
398 
handleCommClosed()399 void Adaptation::Icap::Xaction::handleCommClosed()
400 {
401     detailError(ERR_DETAIL_ICAP_XACT_CLOSE);
402     mustStop("ICAP service connection externally closed");
403 }
404 
callException(const std::exception & e)405 void Adaptation::Icap::Xaction::callException(const std::exception  &e)
406 {
407     setOutcome(xoError);
408     service().noteFailure();
409     Adaptation::Initiate::callException(e);
410 }
411 
callEnd()412 void Adaptation::Icap::Xaction::callEnd()
413 {
414     if (doneWithIo()) {
415         debugs(93, 5, HERE << typeName << " done with I/O" << status());
416         closeConnection();
417     }
418     Adaptation::Initiate::callEnd(); // may destroy us
419 }
420 
doneAll() const421 bool Adaptation::Icap::Xaction::doneAll() const
422 {
423     return !connector && !securer && !reader && !writer && Adaptation::Initiate::doneAll();
424 }
425 
updateTimeout()426 void Adaptation::Icap::Xaction::updateTimeout()
427 {
428     Must(haveConnection());
429 
430     if (reader != NULL || writer != NULL) {
431         // restart the timeout before each I/O
432         // XXX: why does Config.Timeout lacks a write timeout?
433         // TODO: service bypass status may differ from that of a transaction
434         typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommTimeoutCbParams> TimeoutDialer;
435         AsyncCall::Pointer call = JobCallback(93, 5, TimeoutDialer, this, Adaptation::Icap::Xaction::noteCommTimedout);
436         commSetConnTimeout(connection, TheConfig.io_timeout(service().cfg().bypass), call);
437     } else {
438         // clear timeout when there is no I/O
439         // Do we need a lifetime timeout?
440         commUnsetConnTimeout(connection);
441     }
442 }
443 
scheduleRead()444 void Adaptation::Icap::Xaction::scheduleRead()
445 {
446     Must(haveConnection());
447     Must(!reader);
448     Must(readBuf.length() < SQUID_TCP_SO_RCVBUF); // will expand later if needed
449 
450     typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommIoCbParams> Dialer;
451     reader = JobCallback(93, 3, Dialer, this, Adaptation::Icap::Xaction::noteCommRead);
452     Comm::Read(connection, reader);
453     updateTimeout();
454 }
455 
456 // comm module read a portion of the ICAP response for us
noteCommRead(const CommIoCbParams & io)457 void Adaptation::Icap::Xaction::noteCommRead(const CommIoCbParams &io)
458 {
459     Must(reader != NULL);
460     reader = NULL;
461 
462     Must(io.flag == Comm::OK);
463 
464     // TODO: tune this better to expected message sizes
465     readBuf.reserveCapacity(SQUID_TCP_SO_RCVBUF);
466     // we are not asked to grow beyond the allowed maximum
467     Must(readBuf.length() < SQUID_TCP_SO_RCVBUF);
468     // now we can ensure that there is space to read new data,
469     // even if readBuf.spaceSize() currently returns zero.
470     readBuf.rawAppendStart(1);
471 
472     CommIoCbParams rd(this); // will be expanded with ReadNow results
473     rd.conn = io.conn;
474 
475     switch (Comm::ReadNow(rd, readBuf)) {
476     case Comm::INPROGRESS:
477         if (readBuf.isEmpty())
478             debugs(33, 2, io.conn << ": no data to process, " << xstrerr(rd.xerrno));
479         scheduleRead();
480         return;
481 
482     case Comm::OK:
483         al.icap.bytesRead += rd.size;
484 
485         updateTimeout();
486 
487         debugs(93, 3, "read " << rd.size << " bytes");
488 
489         disableRetries(); // because pconn did not fail
490 
491         /* Continue to process previously read data */
492         break;
493 
494     case Comm::ENDFILE: // close detected by 0-byte read
495         commEof = true;
496         reuseConnection = false;
497 
498         // detect a pconn race condition: eof on the first pconn read
499         if (!al.icap.bytesRead && retriable()) {
500             setOutcome(xoRace);
501             mustStop("pconn race");
502             return;
503         }
504 
505         break;
506 
507     // case Comm::COMM_ERROR:
508     default: // no other flags should ever occur
509         debugs(11, 2, io.conn << ": read failure: " << xstrerr(rd.xerrno));
510         mustStop("unknown ICAP I/O read error");
511         return;
512     }
513 
514     handleCommRead(io.size);
515 }
516 
cancelRead()517 void Adaptation::Icap::Xaction::cancelRead()
518 {
519     if (reader != NULL) {
520         Must(haveConnection());
521         Comm::ReadCancel(connection->fd, reader);
522         reader = NULL;
523     }
524 }
525 
parseHttpMsg(HttpMsg * msg)526 bool Adaptation::Icap::Xaction::parseHttpMsg(HttpMsg *msg)
527 {
528     debugs(93, 5, "have " << readBuf.length() << " head bytes to parse");
529 
530     Http::StatusCode error = Http::scNone;
531     // XXX: performance regression c_str() data copies
532     const char *buf = readBuf.c_str();
533     const bool parsed = msg->parse(buf, readBuf.length(), commEof, &error);
534     Must(parsed || !error); // success or need more data
535 
536     if (!parsed) {  // need more data
537         Must(mayReadMore());
538         msg->reset();
539         return false;
540     }
541 
542     readBuf.consume(msg->hdr_sz);
543     return true;
544 }
545 
mayReadMore() const546 bool Adaptation::Icap::Xaction::mayReadMore() const
547 {
548     return !doneReading() && // will read more data
549            readBuf.length() < SQUID_TCP_SO_RCVBUF;  // have space for more data
550 }
551 
doneReading() const552 bool Adaptation::Icap::Xaction::doneReading() const
553 {
554     return commEof;
555 }
556 
doneWriting() const557 bool Adaptation::Icap::Xaction::doneWriting() const
558 {
559     return !writer;
560 }
561 
doneWithIo() const562 bool Adaptation::Icap::Xaction::doneWithIo() const
563 {
564     return haveConnection() &&
565            !connector && !reader && !writer && // fast checks, some redundant
566            doneReading() && doneWriting();
567 }
568 
haveConnection() const569 bool Adaptation::Icap::Xaction::haveConnection() const
570 {
571     return connection != NULL && connection->isOpen();
572 }
573 
574 // initiator aborted
noteInitiatorAborted()575 void Adaptation::Icap::Xaction::noteInitiatorAborted()
576 {
577 
578     if (theInitiator.set()) {
579         debugs(93,4, HERE << "Initiator gone before ICAP transaction ended");
580         clearInitiator();
581         detailError(ERR_DETAIL_ICAP_INIT_GONE);
582         setOutcome(xoGone);
583         mustStop("initiator aborted");
584     }
585 
586 }
587 
setOutcome(const Adaptation::Icap::XactOutcome & xo)588 void Adaptation::Icap::Xaction::setOutcome(const Adaptation::Icap::XactOutcome &xo)
589 {
590     if (al.icap.outcome != xoUnknown) {
591         debugs(93, 3, "WARNING: resetting outcome: from " << al.icap.outcome << " to " << xo);
592     } else {
593         debugs(93, 4, HERE << xo);
594     }
595     al.icap.outcome = xo;
596 }
597 
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.
swanSong()601 void Adaptation::Icap::Xaction::swanSong()
602 {
603     // kids should sing first and then call the parent method.
604     if (cs.valid()) {
605         debugs(93,6, HERE << id << " about to notify ConnOpener!");
606         CallJobHere(93, 3, cs, Comm::ConnOpener, noteAbort);
607         cs = NULL;
608         service().noteConnectionFailed("abort");
609     }
610 
611     closeConnection(); // TODO: rename because we do not always close
612 
613     readBuf.clear();
614 
615     tellQueryAborted();
616 
617     maybeLog();
618 
619     Adaptation::Initiate::swanSong();
620 }
621 
tellQueryAborted()622 void Adaptation::Icap::Xaction::tellQueryAborted()
623 {
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);
631         clearInitiator();
632     }
633 }
634 
maybeLog()635 void Adaptation::Icap::Xaction::maybeLog()
636 {
637     if (IcapLogfileStatus == LOG_ENABLE) {
638         finalizeLogInfo();
639         icapLogLog(alep);
640     }
641 }
642 
finalizeLogInfo()643 void Adaptation::Icap::Xaction::finalizeLogInfo()
644 {
645     //prepare log data
646     al.icp.opcode = ICP_INVALID;
647 
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;
652 
653     tvSub(al.icap.ioTime, icap_tio_start, icap_tio_finish);
654     tvSub(al.icap.trTime, icap_tr_start, current_time);
655 
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();
662     }
663 }
664 
665 // returns a temporary string depicting transaction status, for debugging
status() const666 const char *Adaptation::Icap::Xaction::status() const
667 {
668     static MemBuf buf;
669     buf.reset();
670     buf.append(" [", 2);
671     fillPendingStatus(buf);
672     buf.append("/", 1);
673     fillDoneStatus(buf);
674     buf.appendf(" %s%u]", id.prefix(), id.value);
675     buf.terminate();
676 
677     return buf.content();
678 }
679 
fillPendingStatus(MemBuf & buf) const680 void Adaptation::Icap::Xaction::fillPendingStatus(MemBuf &buf) const
681 {
682     if (haveConnection()) {
683         buf.appendf("FD %d", connection->fd);
684 
685         if (writer != NULL)
686             buf.append("w", 1);
687 
688         if (reader != NULL)
689             buf.append("r", 1);
690 
691         buf.append(";", 1);
692     }
693 }
694 
fillDoneStatus(MemBuf & buf) const695 void Adaptation::Icap::Xaction::fillDoneStatus(MemBuf &buf) const
696 {
697     if (haveConnection() && commEof)
698         buf.appendf("Comm(%d)", connection->fd);
699 
700     if (stopReason != NULL)
701         buf.append("Stopped", 7);
702 }
703 
fillVirginHttpHeader(MemBuf &) const704 bool Adaptation::Icap::Xaction::fillVirginHttpHeader(MemBuf &) const
705 {
706     return false;
707 }
708 
709 bool
initialize(Security::SessionPointer & serverSession)710 Ssl::IcapPeerConnector::initialize(Security::SessionPointer &serverSession)
711 {
712     if (!Security::PeerConnector::initialize(serverSession))
713         return false;
714 
715     assert(!icapService->cfg().secure.sslDomain.isEmpty());
716 #if USE_OPENSSL
717     SBuf *host = new SBuf(icapService->cfg().secure.sslDomain);
718     SSL_set_ex_data(serverSession.get(), ssl_ex_index_server, host);
719     setClientSNI(serverSession.get(), host->c_str());
720 
721     ACLFilledChecklist *check = static_cast<ACLFilledChecklist *>(SSL_get_ex_data(serverSession.get(), ssl_ex_index_cert_error_check));
722     if (check)
723         check->dst_peer_name = *host;
724 #endif
725 
726     Security::SetSessionResumeData(serverSession, icapService->sslSession);
727     return true;
728 }
729 
730 void
noteNegotiationDone(ErrorState * error)731 Ssl::IcapPeerConnector::noteNegotiationDone(ErrorState *error)
732 {
733     if (error)
734         return;
735 
736     const int fd = serverConnection()->fd;
737     Security::MaybeGetSessionResumeData(fd_table[fd].ssl, icapService->sslSession);
738 }
739 
740 void
handleSecuredPeer(Security::EncryptorAnswer & answer)741 Adaptation::Icap::Xaction::handleSecuredPeer(Security::EncryptorAnswer &answer)
742 {
743     Must(securer != NULL);
744     securer = NULL;
745 
746     if (closer != NULL) {
747         if (Comm::IsConnOpen(answer.conn))
748             comm_remove_close_handler(answer.conn->fd, closer);
749         else
750             closer->cancel("securing completed");
751         closer = NULL;
752     }
753 
754     if (answer.error.get()) {
755         if (answer.conn != NULL)
756             answer.conn->close();
757         debugs(93, 2, typeName <<
758                " TLS negotiation to " << service().cfg().uri << " failed");
759         service().noteConnectionFailed("failure");
760         detailError(ERR_DETAIL_ICAP_XACT_SSL_START);
761         throw TexcHere("cannot connect to the TLS ICAP service");
762     }
763 
764     debugs(93, 5, "TLS negotiation to " << service().cfg().uri << " complete");
765 
766     service().noteConnectionUse(answer.conn);
767 
768     handleCommConnected();
769 }
770 
771