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