]> git.ipfire.org Git - thirdparty/squid.git/blob - src/adaptation/icap/Xaction.cc
transaction_initiator ACL for detecting various unusual transactions
[thirdparty/squid.git] / src / adaptation / icap / Xaction.cc
1 /*
2 * Copyright (C) 1996-2017 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 "HttpReply.h"
28 #include "icap_log.h"
29 #include "ipcache.h"
30 #include "pconn.h"
31 #include "security/PeerConnector.h"
32 #include "SquidConfig.h"
33 #include "SquidTime.h"
34
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
38 {
39 public:
40 MyIcapAnswerDialer(const JobPointer &aJob, Method aMethod):
41 UnaryMemFunT<Adaptation::Icap::Xaction, Security::EncryptorAnswer, Security::EncryptorAnswer&>(aJob, aMethod, Security::EncryptorAnswer()) {}
42
43 /* Security::PeerConnector::CbDialer API */
44 virtual Security::EncryptorAnswer &answer() { return arg1; }
45 };
46
47 namespace Ssl
48 {
49 /// A simple PeerConnector for Secure ICAP services. No SslBump capabilities.
50 class IcapPeerConnector: public Security::PeerConnector {
51 CBDATA_CLASS(IcapPeerConnector);
52 public:
53 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) {}
61
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;
67 }
68
69 private:
70 Adaptation::Icap::ServiceRep::Pointer icapService;
71 };
72 } // namespace Ssl
73
74 CBDATA_NAMESPACED_CLASS_INIT(Ssl, IcapPeerConnector);
75
76 Adaptation::Icap::Xaction::Xaction(const char *aTypeName, Adaptation::Icap::ServiceRep::Pointer &aService):
77 AsyncJob(aTypeName),
78 Adaptation::Initiate(aTypeName),
79 icapRequest(NULL),
80 icapReply(NULL),
81 attempts(0),
82 connection(NULL),
83 theService(aService),
84 commEof(false),
85 reuseConnection(true),
86 isRetriable(true),
87 isRepeatable(true),
88 ignoreLastWrite(false),
89 stopReason(NULL),
90 connector(NULL),
91 reader(NULL),
92 writer(NULL),
93 closer(NULL),
94 alep(new AccessLogEntry),
95 al(*alep),
96 cs(NULL)
97 {
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));
106 }
107
108 Adaptation::Icap::Xaction::~Xaction()
109 {
110 debugs(93,3, typeName << " destructed, this=" << this <<
111 " [icapx" << id << ']'); // we should not call virtual status() here
112 HTTPMSGUNLOCK(icapRequest);
113 }
114
115 AccessLogEntry::Pointer
116 Adaptation::Icap::Xaction::masterLogEntry()
117 {
118 AccessLogEntry::Pointer nil;
119 return nil;
120 }
121
122 Adaptation::Icap::ServiceRep &
123 Adaptation::Icap::Xaction::service()
124 {
125 Must(theService != NULL);
126 return *theService;
127 }
128
129 void Adaptation::Icap::Xaction::disableRetries()
130 {
131 debugs(93,5, typeName << (isRetriable ? " from now on" : " still") <<
132 " cannot be retried " << status());
133 isRetriable = false;
134 }
135
136 void Adaptation::Icap::Xaction::disableRepeats(const char *reason)
137 {
138 debugs(93,5, typeName << (isRepeatable ? " from now on" : " still") <<
139 " cannot be repeated because " << reason << status());
140 isRepeatable = false;
141 }
142
143 void Adaptation::Icap::Xaction::start()
144 {
145 Adaptation::Initiate::start();
146 }
147
148 static void
149 icapLookupDnsResults(const ipcache_addrs *ia, const Dns::LookupDetails &, void *data)
150 {
151 Adaptation::Icap::Xaction *xa = static_cast<Adaptation::Icap::Xaction *>(data);
152 xa->dnsLookupDone(ia);
153 }
154
155 // TODO: obey service-specific, OPTIONS-reported connection limit
156 void
157 Adaptation::Icap::Xaction::openConnection()
158 {
159 Must(!haveConnection());
160
161 Adaptation::Icap::ServiceRep &s = service();
162
163 if (!TheConfig.reuse_connections)
164 disableRetries(); // this will also safely drain pconn pool
165
166 bool wasReused = false;
167 connection = s.getConnection(isRetriable, wasReused);
168
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);
181 return;
182 }
183
184 disableRetries(); // we only retry pconn failures
185
186 // Attempt to open a new connection...
187 debugs(93,3, typeName << " opens connection to " << s.cfg().host.termedBuf() << ":" << s.cfg().port);
188
189 // Locate the Service IP(s) to open
190 ipcache_nbgethostbyname(s.cfg().host.termedBuf(), icapLookupDnsResults, this);
191 }
192
193 void
194 Adaptation::Icap::Xaction::dnsLookupDone(const ipcache_addrs *ia)
195 {
196 Adaptation::Icap::ServiceRep &s = service();
197
198 if (ia == NULL) {
199 debugs(44, DBG_IMPORTANT, "ICAP: Unknown service host: " << s.cfg().host);
200
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);
213 #endif
214 return;
215 }
216
217 assert(ia->cur < ia->count);
218
219 connection = new Comm::Connection;
220 connection->remote = ia->in_addrs[ia->cur];
221 connection->remote.port(s.cfg().port);
222 getOutgoingAddress(NULL, connection);
223
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());
230 }
231
232 /*
233 * This event handler is necessary to work around the no-rentry policy
234 * of Adaptation::Icap::Xaction::callStart()
235 */
236 #if 0
237 void
238 Adaptation::Icap::Xaction::reusedConnection(void *data)
239 {
240 debugs(93, 5, HERE << "reused connection");
241 Adaptation::Icap::Xaction *x = (Adaptation::Icap::Xaction*)data;
242 x->noteCommConnected(Comm::OK);
243 }
244 #endif
245
246 void Adaptation::Icap::Xaction::closeConnection()
247 {
248 if (haveConnection()) {
249
250 if (closer != NULL) {
251 comm_remove_close_handler(connection->fd, closer);
252 closer = NULL;
253 }
254
255 cancelRead(); // may not work
256
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;
261 }
262
263 if (reuseConnection)
264 disableRetries();
265
266 const bool reset = !reuseConnection &&
267 (al.icap.outcome == xoGone || al.icap.outcome == xoError);
268
269 Adaptation::Icap::ServiceRep &s = service();
270 s.putConnection(connection, reuseConnection, reset, status());
271
272 writer = NULL;
273 reader = NULL;
274 connector = NULL;
275 connection = NULL;
276 }
277 }
278
279 // connection with the ICAP service established
280 void Adaptation::Icap::Xaction::noteCommConnected(const CommConnectCbParams &io)
281 {
282 cs = NULL;
283
284 if (io.flag == Comm::TIMEOUT) {
285 handleCommTimedout();
286 return;
287 }
288
289 Must(connector != NULL);
290 connector = NULL;
291
292 if (io.flag != Comm::OK)
293 dieOnConnectionFailure(); // throws
294
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);
299
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);
304
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));
312
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
315 return;
316 }
317
318 // ?? fd_table[io.conn->fd].noteUse(icapPconnPool);
319 service().noteConnectionUse(connection);
320
321 handleCommConnected();
322 }
323
324 void Adaptation::Icap::Xaction::dieOnConnectionFailure()
325 {
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");
331 }
332
333 void Adaptation::Icap::Xaction::scheduleWrite(MemBuf &buf)
334 {
335 Must(haveConnection());
336
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);
341
342 Comm::Write(connection, &buf, writer);
343 updateTimeout();
344 }
345
346 void Adaptation::Icap::Xaction::noteCommWrote(const CommIoCbParams &io)
347 {
348 Must(writer != NULL);
349 writer = NULL;
350
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);
355 } else {
356 Must(io.flag == Comm::OK);
357 al.icap.bytesSent += io.size;
358 updateTimeout();
359 handleCommWrote(io.size);
360 }
361 }
362
363 // communication timeout with the ICAP service
364 void Adaptation::Icap::Xaction::noteCommTimedout(const CommTimeoutCbParams &)
365 {
366 handleCommTimedout();
367 }
368
369 void Adaptation::Icap::Xaction::handleCommTimedout()
370 {
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");
379 } else
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");
384 }
385
386 // unexpected connection close while talking to the ICAP service
387 void Adaptation::Icap::Xaction::noteCommClosed(const CommCloseCbParams &)
388 {
389 if (securer != NULL) {
390 securer->cancel("Connection closed before SSL negotiation finished");
391 securer = NULL;
392 }
393 closer = NULL;
394 handleCommClosed();
395 }
396
397 void Adaptation::Icap::Xaction::handleCommClosed()
398 {
399 detailError(ERR_DETAIL_ICAP_XACT_CLOSE);
400 mustStop("ICAP service connection externally closed");
401 }
402
403 void Adaptation::Icap::Xaction::callException(const std::exception &e)
404 {
405 setOutcome(xoError);
406 service().noteFailure();
407 Adaptation::Initiate::callException(e);
408 }
409
410 void Adaptation::Icap::Xaction::callEnd()
411 {
412 if (doneWithIo()) {
413 debugs(93, 5, HERE << typeName << " done with I/O" << status());
414 closeConnection();
415 }
416 Adaptation::Initiate::callEnd(); // may destroy us
417 }
418
419 bool Adaptation::Icap::Xaction::doneAll() const
420 {
421 return !connector && !securer && !reader && !writer && Adaptation::Initiate::doneAll();
422 }
423
424 void Adaptation::Icap::Xaction::updateTimeout()
425 {
426 Must(haveConnection());
427
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);
435 } else {
436 // clear timeout when there is no I/O
437 // Do we need a lifetime timeout?
438 commUnsetConnTimeout(connection);
439 }
440 }
441
442 void Adaptation::Icap::Xaction::scheduleRead()
443 {
444 Must(haveConnection());
445 Must(!reader);
446 Must(readBuf.length() < SQUID_TCP_SO_RCVBUF); // will expand later if needed
447
448 typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommIoCbParams> Dialer;
449 reader = JobCallback(93, 3, Dialer, this, Adaptation::Icap::Xaction::noteCommRead);
450 Comm::Read(connection, reader);
451 updateTimeout();
452 }
453
454 // comm module read a portion of the ICAP response for us
455 void Adaptation::Icap::Xaction::noteCommRead(const CommIoCbParams &io)
456 {
457 Must(reader != NULL);
458 reader = NULL;
459
460 Must(io.flag == Comm::OK);
461
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.
468 readBuf.rawSpace(1);
469
470 CommIoCbParams rd(this); // will be expanded with ReadNow results
471 rd.conn = io.conn;
472
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));
477 scheduleRead();
478 return;
479
480 case Comm::OK:
481 al.icap.bytesRead += rd.size;
482
483 updateTimeout();
484
485 debugs(93, 3, "read " << rd.size << " bytes");
486
487 disableRetries(); // because pconn did not fail
488
489 /* Continue to process previously read data */
490 break;
491
492 case Comm::ENDFILE: // close detected by 0-byte read
493 commEof = true;
494 reuseConnection = false;
495
496 // detect a pconn race condition: eof on the first pconn read
497 if (!al.icap.bytesRead && retriable()) {
498 setOutcome(xoRace);
499 mustStop("pconn race");
500 return;
501 }
502
503 break;
504
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");
509 return;
510 }
511
512 handleCommRead(io.size);
513 }
514
515 void Adaptation::Icap::Xaction::cancelRead()
516 {
517 if (reader != NULL) {
518 Must(haveConnection());
519 Comm::ReadCancel(connection->fd, reader);
520 reader = NULL;
521 }
522 }
523
524 bool
525 Adaptation::Icap::Xaction::parseHttpMsg(Http::Message *msg)
526 {
527 debugs(93, 5, "have " << readBuf.length() << " head bytes to parse");
528
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
534
535 if (!parsed) { // need more data
536 Must(mayReadMore());
537 msg->reset();
538 return false;
539 }
540
541 readBuf.consume(msg->hdr_sz);
542 return true;
543 }
544
545 bool Adaptation::Icap::Xaction::mayReadMore() const
546 {
547 return !doneReading() && // will read more data
548 readBuf.length() < SQUID_TCP_SO_RCVBUF; // have space for more data
549 }
550
551 bool Adaptation::Icap::Xaction::doneReading() const
552 {
553 return commEof;
554 }
555
556 bool Adaptation::Icap::Xaction::doneWriting() const
557 {
558 return !writer;
559 }
560
561 bool Adaptation::Icap::Xaction::doneWithIo() const
562 {
563 return haveConnection() &&
564 !connector && !reader && !writer && // fast checks, some redundant
565 doneReading() && doneWriting();
566 }
567
568 bool Adaptation::Icap::Xaction::haveConnection() const
569 {
570 return connection != NULL && connection->isOpen();
571 }
572
573 // initiator aborted
574 void Adaptation::Icap::Xaction::noteInitiatorAborted()
575 {
576
577 if (theInitiator.set()) {
578 debugs(93,4, HERE << "Initiator gone before ICAP transaction ended");
579 clearInitiator();
580 detailError(ERR_DETAIL_ICAP_INIT_GONE);
581 setOutcome(xoGone);
582 mustStop("initiator aborted");
583 }
584
585 }
586
587 void Adaptation::Icap::Xaction::setOutcome(const Adaptation::Icap::XactOutcome &xo)
588 {
589 if (al.icap.outcome != xoUnknown) {
590 debugs(93, 3, HERE << "Warning: reseting outcome: from " <<
591 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.
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
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
635 void Adaptation::Icap::Xaction::maybeLog()
636 {
637 if (IcapLogfileStatus == LOG_ENABLE) {
638 finalizeLogInfo();
639 icapLogLog(alep);
640 }
641 }
642
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
666 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
680 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
695 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
704 bool Adaptation::Icap::Xaction::fillVirginHttpHeader(MemBuf &) const
705 {
706 return false;
707 }
708
709 bool
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
720 ACLFilledChecklist *check = static_cast<ACLFilledChecklist *>(SSL_get_ex_data(serverSession.get(), ssl_ex_index_cert_error_check));
721 if (check)
722 check->dst_peer_name = *host;
723 #endif
724
725 Security::SetSessionResumeData(serverSession, icapService->sslSession);
726 return true;
727 }
728
729 void
730 Ssl::IcapPeerConnector::noteNegotiationDone(ErrorState *error)
731 {
732 if (error)
733 return;
734
735 const int fd = serverConnection()->fd;
736 Security::MaybeGetSessionResumeData(fd_table[fd].ssl, icapService->sslSession);
737 }
738
739 void
740 Adaptation::Icap::Xaction::handleSecuredPeer(Security::EncryptorAnswer &answer)
741 {
742 Must(securer != NULL);
743 securer = NULL;
744
745 if (closer != NULL) {
746 if (answer.conn != NULL)
747 comm_remove_close_handler(answer.conn->fd, closer);
748 else
749 closer->cancel("securing completed");
750 closer = NULL;
751 }
752
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");
761 }
762
763 debugs(93, 5, "TLS negotiation to " << service().cfg().uri << " complete");
764
765 service().noteConnectionUse(answer.conn);
766
767 handleCommConnected();
768 }
769