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