1 | /* |
---|
2 | * Copyright (C) 1996-2015 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 33 Client-side Routines */ |
---|
10 | |
---|
11 | /** |
---|
12 | \defgroup ClientSide Client-Side Logics |
---|
13 | * |
---|
14 | \section cserrors Errors and client side |
---|
15 | * |
---|
16 | \par Problem the first: |
---|
17 | * the store entry is no longer authoritative on the |
---|
18 | * reply status. EBITTEST (E_ABORT) is no longer a valid test outside |
---|
19 | * of client_side_reply.c. |
---|
20 | * Problem the second: resources are wasted if we delay in cleaning up. |
---|
21 | * Problem the third we can't depend on a connection close to clean up. |
---|
22 | * |
---|
23 | \par Nice thing the first: |
---|
24 | * Any step in the stream can callback with data |
---|
25 | * representing an error. |
---|
26 | * Nice thing the second: once you stop requesting reads from upstream, |
---|
27 | * upstream can be stopped too. |
---|
28 | * |
---|
29 | \par Solution #1: |
---|
30 | * Error has a callback mechanism to hand over a membuf |
---|
31 | * with the error content. The failing node pushes that back as the |
---|
32 | * reply. Can this be generalised to reduce duplicate efforts? |
---|
33 | * A: Possibly. For now, only one location uses this. |
---|
34 | * How to deal with pre-stream errors? |
---|
35 | * Tell client_side_reply that we *want* an error page before any |
---|
36 | * stream calls occur. Then we simply read as normal. |
---|
37 | * |
---|
38 | * |
---|
39 | \section pconn_logic Persistent connection logic: |
---|
40 | * |
---|
41 | \par |
---|
42 | * requests (httpClientRequest structs) get added to the connection |
---|
43 | * list, with the current one being chr |
---|
44 | * |
---|
45 | \par |
---|
46 | * The request is *immediately* kicked off, and data flows through |
---|
47 | * to clientSocketRecipient. |
---|
48 | * |
---|
49 | \par |
---|
50 | * If the data that arrives at clientSocketRecipient is not for the current |
---|
51 | * request, clientSocketRecipient simply returns, without requesting more |
---|
52 | * data, or sending it. |
---|
53 | * |
---|
54 | \par |
---|
55 | * ClientKeepAliveNextRequest will then detect the presence of data in |
---|
56 | * the next ClientHttpRequest, and will send it, restablishing the |
---|
57 | * data flow. |
---|
58 | */ |
---|
59 | |
---|
60 | #include "squid.h" |
---|
61 | #include "acl/FilledChecklist.h" |
---|
62 | #include "anyp/PortCfg.h" |
---|
63 | #include "base/Subscription.h" |
---|
64 | #include "base/TextException.h" |
---|
65 | #include "CachePeer.h" |
---|
66 | #include "ChunkedCodingParser.h" |
---|
67 | #include "client_db.h" |
---|
68 | #include "client_side.h" |
---|
69 | #include "client_side_reply.h" |
---|
70 | #include "client_side_request.h" |
---|
71 | #include "ClientRequestContext.h" |
---|
72 | #include "clientStream.h" |
---|
73 | #include "comm.h" |
---|
74 | #include "comm/Connection.h" |
---|
75 | #include "comm/Loops.h" |
---|
76 | #include "comm/Read.h" |
---|
77 | #include "comm/TcpAcceptor.h" |
---|
78 | #include "comm/Write.h" |
---|
79 | #include "CommCalls.h" |
---|
80 | #include "errorpage.h" |
---|
81 | #include "fd.h" |
---|
82 | #include "fde.h" |
---|
83 | #include "fqdncache.h" |
---|
84 | #include "FwdState.h" |
---|
85 | #include "globals.h" |
---|
86 | #include "helper.h" |
---|
87 | #include "helper/Reply.h" |
---|
88 | #include "http.h" |
---|
89 | #include "HttpHdrContRange.h" |
---|
90 | #include "HttpHeaderTools.h" |
---|
91 | #include "HttpReply.h" |
---|
92 | #include "HttpRequest.h" |
---|
93 | #include "ident/Config.h" |
---|
94 | #include "ident/Ident.h" |
---|
95 | #include "internal.h" |
---|
96 | #include "ipc/FdNotes.h" |
---|
97 | #include "ipc/StartListening.h" |
---|
98 | #include "log/access_log.h" |
---|
99 | #include "Mem.h" |
---|
100 | #include "MemBuf.h" |
---|
101 | #include "MemObject.h" |
---|
102 | #include "mime_header.h" |
---|
103 | #include "parser/Tokenizer.h" |
---|
104 | #include "profiler/Profiler.h" |
---|
105 | #include "rfc1738.h" |
---|
106 | #include "servers/forward.h" |
---|
107 | #include "SquidConfig.h" |
---|
108 | #include "SquidTime.h" |
---|
109 | #include "StatCounters.h" |
---|
110 | #include "StatHist.h" |
---|
111 | #include "Store.h" |
---|
112 | #include "TimeOrTag.h" |
---|
113 | #include "tools.h" |
---|
114 | #include "URL.h" |
---|
115 | |
---|
116 | #if USE_AUTH |
---|
117 | #include "auth/UserRequest.h" |
---|
118 | #endif |
---|
119 | #if USE_DELAY_POOLS |
---|
120 | #include "ClientInfo.h" |
---|
121 | #endif |
---|
122 | #if USE_OPENSSL |
---|
123 | #include "ssl/bio.h" |
---|
124 | #include "ssl/context_storage.h" |
---|
125 | #include "ssl/gadgets.h" |
---|
126 | #include "ssl/helper.h" |
---|
127 | #include "ssl/ProxyCerts.h" |
---|
128 | #include "ssl/ServerBump.h" |
---|
129 | #include "ssl/support.h" |
---|
130 | #endif |
---|
131 | #if USE_SSL_CRTD |
---|
132 | #include "ssl/certificate_db.h" |
---|
133 | #include "ssl/crtd_message.h" |
---|
134 | #endif |
---|
135 | |
---|
136 | #include <climits> |
---|
137 | #include <cmath> |
---|
138 | #include <limits> |
---|
139 | |
---|
140 | #if LINGERING_CLOSE |
---|
141 | #define comm_close comm_lingering_close |
---|
142 | #endif |
---|
143 | |
---|
144 | /// dials clientListenerConnectionOpened call |
---|
145 | class ListeningStartedDialer: public CallDialer, public Ipc::StartListeningCb |
---|
146 | { |
---|
147 | public: |
---|
148 | typedef void (*Handler)(AnyP::PortCfgPointer &portCfg, const Ipc::FdNoteId note, const Subscription::Pointer &sub); |
---|
149 | ListeningStartedDialer(Handler aHandler, AnyP::PortCfgPointer &aPortCfg, const Ipc::FdNoteId note, const Subscription::Pointer &aSub): |
---|
150 | handler(aHandler), portCfg(aPortCfg), portTypeNote(note), sub(aSub) {} |
---|
151 | |
---|
152 | virtual void print(std::ostream &os) const { |
---|
153 | startPrint(os) << |
---|
154 | ", " << FdNote(portTypeNote) << " port=" << (void*)&portCfg << ')'; |
---|
155 | } |
---|
156 | |
---|
157 | virtual bool canDial(AsyncCall &) const { return true; } |
---|
158 | virtual void dial(AsyncCall &) { (handler)(portCfg, portTypeNote, sub); } |
---|
159 | |
---|
160 | public: |
---|
161 | Handler handler; |
---|
162 | |
---|
163 | private: |
---|
164 | AnyP::PortCfgPointer portCfg; ///< from HttpPortList |
---|
165 | Ipc::FdNoteId portTypeNote; ///< Type of IPC socket being opened |
---|
166 | Subscription::Pointer sub; ///< The handler to be subscribed for this connetion listener |
---|
167 | }; |
---|
168 | |
---|
169 | static void clientListenerConnectionOpened(AnyP::PortCfgPointer &s, const Ipc::FdNoteId portTypeNote, const Subscription::Pointer &sub); |
---|
170 | |
---|
171 | /* our socket-related context */ |
---|
172 | |
---|
173 | CBDATA_CLASS_INIT(ClientSocketContext); |
---|
174 | |
---|
175 | /* Local functions */ |
---|
176 | static IOCB clientWriteComplete; |
---|
177 | static IOCB clientWriteBodyComplete; |
---|
178 | static IOACB httpAccept; |
---|
179 | #if USE_OPENSSL |
---|
180 | static IOACB httpsAccept; |
---|
181 | #endif |
---|
182 | static CTCB clientLifetimeTimeout; |
---|
183 | #if USE_IDENT |
---|
184 | static IDCB clientIdentDone; |
---|
185 | #endif |
---|
186 | static int clientIsContentLengthValid(HttpRequest * r); |
---|
187 | static int clientIsRequestBodyTooLargeForPolicy(int64_t bodyLength); |
---|
188 | |
---|
189 | static void clientUpdateStatHistCounters(LogTags logType, int svc_time); |
---|
190 | static void clientUpdateStatCounters(LogTags logType); |
---|
191 | static void clientUpdateHierCounters(HierarchyLogEntry *); |
---|
192 | static bool clientPingHasFinished(ping_data const *aPing); |
---|
193 | void prepareLogWithRequestDetails(HttpRequest *, AccessLogEntry::Pointer &); |
---|
194 | static void ClientSocketContextPushDeferredIfNeeded(ClientSocketContext::Pointer deferredRequest, ConnStateData * conn); |
---|
195 | static void clientUpdateSocketStats(LogTags logType, size_t size); |
---|
196 | |
---|
197 | char *skipLeadingSpace(char *aString); |
---|
198 | static void connNoteUseOfBuffer(ConnStateData* conn, size_t byteCount); |
---|
199 | |
---|
200 | clientStreamNode * |
---|
201 | ClientSocketContext::getTail() const |
---|
202 | { |
---|
203 | if (http->client_stream.tail) |
---|
204 | return (clientStreamNode *)http->client_stream.tail->data; |
---|
205 | |
---|
206 | return NULL; |
---|
207 | } |
---|
208 | |
---|
209 | clientStreamNode * |
---|
210 | ClientSocketContext::getClientReplyContext() const |
---|
211 | { |
---|
212 | return (clientStreamNode *)http->client_stream.tail->prev->data; |
---|
213 | } |
---|
214 | |
---|
215 | ConnStateData * |
---|
216 | ClientSocketContext::getConn() const |
---|
217 | { |
---|
218 | return http->getConn(); |
---|
219 | } |
---|
220 | |
---|
221 | /** |
---|
222 | * This routine should be called to grow the in.buf and then |
---|
223 | * call Comm::Read(). |
---|
224 | */ |
---|
225 | void |
---|
226 | ConnStateData::readSomeData() |
---|
227 | { |
---|
228 | if (reading()) |
---|
229 | return; |
---|
230 | |
---|
231 | debugs(33, 4, HERE << clientConnection << ": reading request..."); |
---|
232 | |
---|
233 | // we can only read if there is more than 1 byte of space free |
---|
234 | if (Config.maxRequestBufferSize - in.buf.length() < 2) |
---|
235 | return; |
---|
236 | |
---|
237 | typedef CommCbMemFunT<ConnStateData, CommIoCbParams> Dialer; |
---|
238 | reader = JobCallback(33, 5, Dialer, this, ConnStateData::clientReadRequest); |
---|
239 | Comm::Read(clientConnection, reader); |
---|
240 | } |
---|
241 | |
---|
242 | void |
---|
243 | ClientSocketContext::removeFromConnectionList(ConnStateData * conn) |
---|
244 | { |
---|
245 | ClientSocketContext::Pointer *tempContextPointer; |
---|
246 | assert(conn != NULL && cbdataReferenceValid(conn)); |
---|
247 | assert(conn->getCurrentContext() != NULL); |
---|
248 | /* Unlink us from the connection request list */ |
---|
249 | tempContextPointer = & conn->currentobject; |
---|
250 | |
---|
251 | while (tempContextPointer->getRaw()) { |
---|
252 | if (*tempContextPointer == this) |
---|
253 | break; |
---|
254 | |
---|
255 | tempContextPointer = &(*tempContextPointer)->next; |
---|
256 | } |
---|
257 | |
---|
258 | assert(tempContextPointer->getRaw() != NULL); |
---|
259 | *tempContextPointer = next; |
---|
260 | next = NULL; |
---|
261 | } |
---|
262 | |
---|
263 | ClientSocketContext::~ClientSocketContext() |
---|
264 | { |
---|
265 | clientStreamNode *node = getTail(); |
---|
266 | |
---|
267 | if (node) { |
---|
268 | ClientSocketContext *streamContext = dynamic_cast<ClientSocketContext *> (node->data.getRaw()); |
---|
269 | |
---|
270 | if (streamContext) { |
---|
271 | /* We are *always* the tail - prevent recursive free */ |
---|
272 | assert(this == streamContext); |
---|
273 | node->data = NULL; |
---|
274 | } |
---|
275 | } |
---|
276 | |
---|
277 | if (connRegistered_) |
---|
278 | deRegisterWithConn(); |
---|
279 | |
---|
280 | httpRequestFree(http); |
---|
281 | |
---|
282 | /* clean up connection links to us */ |
---|
283 | assert(this != next.getRaw()); |
---|
284 | } |
---|
285 | |
---|
286 | void |
---|
287 | ClientSocketContext::registerWithConn() |
---|
288 | { |
---|
289 | assert (!connRegistered_); |
---|
290 | assert (http); |
---|
291 | assert (http->getConn() != NULL); |
---|
292 | connRegistered_ = true; |
---|
293 | http->getConn()->addContextToQueue(this); |
---|
294 | } |
---|
295 | |
---|
296 | void |
---|
297 | ClientSocketContext::deRegisterWithConn() |
---|
298 | { |
---|
299 | assert (connRegistered_); |
---|
300 | removeFromConnectionList(http->getConn()); |
---|
301 | connRegistered_ = false; |
---|
302 | } |
---|
303 | |
---|
304 | void |
---|
305 | ClientSocketContext::connIsFinished() |
---|
306 | { |
---|
307 | assert (http); |
---|
308 | assert (http->getConn() != NULL); |
---|
309 | deRegisterWithConn(); |
---|
310 | /* we can't handle any more stream data - detach */ |
---|
311 | clientStreamDetach(getTail(), http); |
---|
312 | } |
---|
313 | |
---|
314 | ClientSocketContext::ClientSocketContext(const Comm::ConnectionPointer &aConn, ClientHttpRequest *aReq) : |
---|
315 | clientConnection(aConn), |
---|
316 | http(aReq), |
---|
317 | reply(NULL), |
---|
318 | next(NULL), |
---|
319 | writtenToSocket(0), |
---|
320 | mayUseConnection_ (false), |
---|
321 | connRegistered_ (false) |
---|
322 | { |
---|
323 | assert(http != NULL); |
---|
324 | memset (reqbuf, '\0', sizeof (reqbuf)); |
---|
325 | flags.deferred = 0; |
---|
326 | flags.parsed_ok = 0; |
---|
327 | deferredparams.node = NULL; |
---|
328 | deferredparams.rep = NULL; |
---|
329 | } |
---|
330 | |
---|
331 | void |
---|
332 | ClientSocketContext::writeControlMsg(HttpControlMsg &msg) |
---|
333 | { |
---|
334 | HttpReply::Pointer rep(msg.reply); |
---|
335 | Must(rep != NULL); |
---|
336 | |
---|
337 | // remember the callback |
---|
338 | cbControlMsgSent = msg.cbSuccess; |
---|
339 | |
---|
340 | AsyncCall::Pointer call = commCbCall(33, 5, "ClientSocketContext::wroteControlMsg", |
---|
341 | CommIoCbPtrFun(&WroteControlMsg, this)); |
---|
342 | |
---|
343 | getConn()->writeControlMsgAndCall(this, rep.getRaw(), call); |
---|
344 | } |
---|
345 | |
---|
346 | /// called when we wrote the 1xx response |
---|
347 | void |
---|
348 | ClientSocketContext::wroteControlMsg(const Comm::ConnectionPointer &conn, char *, size_t, Comm::Flag errflag, int xerrno) |
---|
349 | { |
---|
350 | if (errflag == Comm::ERR_CLOSING) |
---|
351 | return; |
---|
352 | |
---|
353 | if (errflag == Comm::OK) { |
---|
354 | ScheduleCallHere(cbControlMsgSent); |
---|
355 | return; |
---|
356 | } |
---|
357 | |
---|
358 | debugs(33, 3, HERE << "1xx writing failed: " << xstrerr(xerrno)); |
---|
359 | // no error notification: see HttpControlMsg.h for rationale and |
---|
360 | // note that some errors are detected elsewhere (e.g., close handler) |
---|
361 | |
---|
362 | // close on 1xx errors to be conservative and to simplify the code |
---|
363 | // (if we do not close, we must notify the source of a failure!) |
---|
364 | conn->close(); |
---|
365 | |
---|
366 | // XXX: writeControlMsgAndCall() should handle writer-specific writing |
---|
367 | // results, including errors and then call us with success/failure outcome. |
---|
368 | } |
---|
369 | |
---|
370 | /// wroteControlMsg() wrapper: ClientSocketContext is not an AsyncJob |
---|
371 | void |
---|
372 | ClientSocketContext::WroteControlMsg(const Comm::ConnectionPointer &conn, char *bufnotused, size_t size, Comm::Flag errflag, int xerrno, void *data) |
---|
373 | { |
---|
374 | ClientSocketContext *context = static_cast<ClientSocketContext*>(data); |
---|
375 | context->wroteControlMsg(conn, bufnotused, size, errflag, xerrno); |
---|
376 | } |
---|
377 | |
---|
378 | #if USE_IDENT |
---|
379 | static void |
---|
380 | clientIdentDone(const char *ident, void *data) |
---|
381 | { |
---|
382 | ConnStateData *conn = (ConnStateData *)data; |
---|
383 | xstrncpy(conn->clientConnection->rfc931, ident ? ident : dash_str, USER_IDENT_SZ); |
---|
384 | } |
---|
385 | #endif |
---|
386 | |
---|
387 | void |
---|
388 | clientUpdateStatCounters(LogTags logType) |
---|
389 | { |
---|
390 | ++statCounter.client_http.requests; |
---|
391 | |
---|
392 | if (logTypeIsATcpHit(logType)) |
---|
393 | ++statCounter.client_http.hits; |
---|
394 | |
---|
395 | if (logType == LOG_TCP_HIT) |
---|
396 | ++statCounter.client_http.disk_hits; |
---|
397 | else if (logType == LOG_TCP_MEM_HIT) |
---|
398 | ++statCounter.client_http.mem_hits; |
---|
399 | } |
---|
400 | |
---|
401 | void |
---|
402 | clientUpdateStatHistCounters(LogTags logType, int svc_time) |
---|
403 | { |
---|
404 | statCounter.client_http.allSvcTime.count(svc_time); |
---|
405 | /** |
---|
406 | * The idea here is not to be complete, but to get service times |
---|
407 | * for only well-defined types. For example, we don't include |
---|
408 | * LOG_TCP_REFRESH_FAIL because its not really a cache hit |
---|
409 | * (we *tried* to validate it, but failed). |
---|
410 | */ |
---|
411 | |
---|
412 | switch (logType) { |
---|
413 | |
---|
414 | case LOG_TCP_REFRESH_UNMODIFIED: |
---|
415 | statCounter.client_http.nearHitSvcTime.count(svc_time); |
---|
416 | break; |
---|
417 | |
---|
418 | case LOG_TCP_IMS_HIT: |
---|
419 | statCounter.client_http.nearMissSvcTime.count(svc_time); |
---|
420 | break; |
---|
421 | |
---|
422 | case LOG_TCP_HIT: |
---|
423 | |
---|
424 | case LOG_TCP_MEM_HIT: |
---|
425 | |
---|
426 | case LOG_TCP_OFFLINE_HIT: |
---|
427 | statCounter.client_http.hitSvcTime.count(svc_time); |
---|
428 | break; |
---|
429 | |
---|
430 | case LOG_TCP_MISS: |
---|
431 | |
---|
432 | case LOG_TCP_CLIENT_REFRESH_MISS: |
---|
433 | statCounter.client_http.missSvcTime.count(svc_time); |
---|
434 | break; |
---|
435 | |
---|
436 | default: |
---|
437 | /* make compiler warnings go away */ |
---|
438 | break; |
---|
439 | } |
---|
440 | } |
---|
441 | |
---|
442 | bool |
---|
443 | clientPingHasFinished(ping_data const *aPing) |
---|
444 | { |
---|
445 | if (0 != aPing->stop.tv_sec && 0 != aPing->start.tv_sec) |
---|
446 | return true; |
---|
447 | |
---|
448 | return false; |
---|
449 | } |
---|
450 | |
---|
451 | void |
---|
452 | clientUpdateHierCounters(HierarchyLogEntry * someEntry) |
---|
453 | { |
---|
454 | ping_data *i; |
---|
455 | |
---|
456 | switch (someEntry->code) { |
---|
457 | #if USE_CACHE_DIGESTS |
---|
458 | |
---|
459 | case CD_PARENT_HIT: |
---|
460 | |
---|
461 | case CD_SIBLING_HIT: |
---|
462 | ++ statCounter.cd.times_used; |
---|
463 | break; |
---|
464 | #endif |
---|
465 | |
---|
466 | case SIBLING_HIT: |
---|
467 | |
---|
468 | case PARENT_HIT: |
---|
469 | |
---|
470 | case FIRST_PARENT_MISS: |
---|
471 | |
---|
472 | case CLOSEST_PARENT_MISS: |
---|
473 | ++ statCounter.icp.times_used; |
---|
474 | i = &someEntry->ping; |
---|
475 | |
---|
476 | if (clientPingHasFinished(i)) |
---|
477 | statCounter.icp.querySvcTime.count(tvSubUsec(i->start, i->stop)); |
---|
478 | |
---|
479 | if (i->timeout) |
---|
480 | ++ statCounter.icp.query_timeouts; |
---|
481 | |
---|
482 | break; |
---|
483 | |
---|
484 | case CLOSEST_PARENT: |
---|
485 | |
---|
486 | case CLOSEST_DIRECT: |
---|
487 | ++ statCounter.netdb.times_used; |
---|
488 | |
---|
489 | break; |
---|
490 | |
---|
491 | default: |
---|
492 | break; |
---|
493 | } |
---|
494 | } |
---|
495 | |
---|
496 | void |
---|
497 | ClientHttpRequest::updateCounters() |
---|
498 | { |
---|
499 | clientUpdateStatCounters(logType); |
---|
500 | |
---|
501 | if (request->errType != ERR_NONE) |
---|
502 | ++ statCounter.client_http.errors; |
---|
503 | |
---|
504 | clientUpdateStatHistCounters(logType, |
---|
505 | tvSubMsec(al->cache.start_time, current_time)); |
---|
506 | |
---|
507 | clientUpdateHierCounters(&request->hier); |
---|
508 | } |
---|
509 | |
---|
510 | void |
---|
511 | prepareLogWithRequestDetails(HttpRequest * request, AccessLogEntry::Pointer &aLogEntry) |
---|
512 | { |
---|
513 | assert(request); |
---|
514 | assert(aLogEntry != NULL); |
---|
515 | |
---|
516 | if (Config.onoff.log_mime_hdrs) { |
---|
517 | Packer p; |
---|
518 | MemBuf mb; |
---|
519 | mb.init(); |
---|
520 | packerToMemInit(&p, &mb); |
---|
521 | request->header.packInto(&p); |
---|
522 | //This is the request after adaptation or redirection |
---|
523 | aLogEntry->headers.adapted_request = xstrdup(mb.buf); |
---|
524 | |
---|
525 | // the virgin request is saved to aLogEntry->request |
---|
526 | if (aLogEntry->request) { |
---|
527 | packerClean(&p); |
---|
528 | mb.reset(); |
---|
529 | packerToMemInit(&p, &mb); |
---|
530 | aLogEntry->request->header.packInto(&p); |
---|
531 | aLogEntry->headers.request = xstrdup(mb.buf); |
---|
532 | } |
---|
533 | |
---|
534 | #if USE_ADAPTATION |
---|
535 | const Adaptation::History::Pointer ah = request->adaptLogHistory(); |
---|
536 | if (ah != NULL) { |
---|
537 | packerClean(&p); |
---|
538 | mb.reset(); |
---|
539 | packerToMemInit(&p, &mb); |
---|
540 | ah->lastMeta.packInto(&p); |
---|
541 | aLogEntry->adapt.last_meta = xstrdup(mb.buf); |
---|
542 | } |
---|
543 | #endif |
---|
544 | |
---|
545 | packerClean(&p); |
---|
546 | mb.clean(); |
---|
547 | } |
---|
548 | |
---|
549 | #if ICAP_CLIENT |
---|
550 | const Adaptation::Icap::History::Pointer ih = request->icapHistory(); |
---|
551 | if (ih != NULL) |
---|
552 | aLogEntry->icap.processingTime = ih->processingTime(); |
---|
553 | #endif |
---|
554 | |
---|
555 | aLogEntry->http.method = request->method; |
---|
556 | aLogEntry->http.version = request->http_ver; |
---|
557 | aLogEntry->hier = request->hier; |
---|
558 | if (request->content_length > 0) // negative when no body or unknown length |
---|
559 | aLogEntry->http.clientRequestSz.payloadData += request->content_length; // XXX: actually adaptedRequest payload size ?? |
---|
560 | aLogEntry->cache.extuser = request->extacl_user.termedBuf(); |
---|
561 | |
---|
562 | // Adapted request, if any, inherits and then collects all the stats, but |
---|
563 | // the virgin request gets logged instead; copy the stats to log them. |
---|
564 | // TODO: avoid losses by keeping these stats in a shared history object? |
---|
565 | if (aLogEntry->request) { |
---|
566 | aLogEntry->request->dnsWait = request->dnsWait; |
---|
567 | aLogEntry->request->errType = request->errType; |
---|
568 | aLogEntry->request->errDetail = request->errDetail; |
---|
569 | } |
---|
570 | } |
---|
571 | |
---|
572 | void |
---|
573 | ClientHttpRequest::logRequest() |
---|
574 | { |
---|
575 | if (!out.size && !logType) |
---|
576 | debugs(33, 5, HERE << "logging half-baked transaction: " << log_uri); |
---|
577 | |
---|
578 | al->icp.opcode = ICP_INVALID; |
---|
579 | al->url = log_uri; |
---|
580 | debugs(33, 9, "clientLogRequest: al.url='" << al->url << "'"); |
---|
581 | |
---|
582 | if (al->reply) { |
---|
583 | al->http.code = al->reply->sline.status(); |
---|
584 | al->http.content_type = al->reply->content_type.termedBuf(); |
---|
585 | } else if (loggingEntry() && loggingEntry()->mem_obj) { |
---|
586 | al->http.code = loggingEntry()->mem_obj->getReply()->sline.status(); |
---|
587 | al->http.content_type = loggingEntry()->mem_obj->getReply()->content_type.termedBuf(); |
---|
588 | } |
---|
589 | |
---|
590 | debugs(33, 9, "clientLogRequest: http.code='" << al->http.code << "'"); |
---|
591 | |
---|
592 | if (loggingEntry() && loggingEntry()->mem_obj && loggingEntry()->objectLen() >= 0) |
---|
593 | al->cache.objectSize = loggingEntry()->contentLen(); // payload duplicate ?? with or without TE ? |
---|
594 | |
---|
595 | al->http.clientRequestSz.header = req_sz; |
---|
596 | al->http.clientReplySz.header = out.headers_sz; |
---|
597 | // XXX: calculate without payload encoding or headers !! |
---|
598 | al->http.clientReplySz.payloadData = out.size - out.headers_sz; // pretend its all un-encoded data for now. |
---|
599 | |
---|
600 | al->cache.highOffset = out.offset; |
---|
601 | |
---|
602 | al->cache.code = logType; |
---|
603 | |
---|
604 | al->cache.msec = tvSubMsec(al->cache.start_time, current_time); |
---|
605 | |
---|
606 | if (request) |
---|
607 | prepareLogWithRequestDetails(request, al); |
---|
608 | |
---|
609 | if (getConn() != NULL && getConn()->clientConnection != NULL && getConn()->clientConnection->rfc931[0]) |
---|
610 | al->cache.rfc931 = getConn()->clientConnection->rfc931; |
---|
611 | |
---|
612 | #if USE_OPENSSL && 0 |
---|
613 | |
---|
614 | /* This is broken. Fails if the connection has been closed. Needs |
---|
615 | * to snarf the ssl details some place earlier.. |
---|
616 | */ |
---|
617 | if (getConn() != NULL) |
---|
618 | al->cache.ssluser = sslGetUserEmail(fd_table[getConn()->fd].ssl); |
---|
619 | |
---|
620 | #endif |
---|
621 | |
---|
622 | /* Add notes (if we have a request to annotate) */ |
---|
623 | if (request) { |
---|
624 | // The al->notes and request->notes must point to the same object. |
---|
625 | (void)SyncNotes(*al, *request); |
---|
626 | for (Notes::iterator i = Config.notes.begin(); i != Config.notes.end(); ++i) { |
---|
627 | if (const char *value = (*i)->match(request, al->reply, NULL)) { |
---|
628 | NotePairs ¬es = SyncNotes(*al, *request); |
---|
629 | notes.add((*i)->key.termedBuf(), value); |
---|
630 | debugs(33, 3, (*i)->key.termedBuf() << " " << value); |
---|
631 | } |
---|
632 | } |
---|
633 | } |
---|
634 | |
---|
635 | ACLFilledChecklist checklist(NULL, request, NULL); |
---|
636 | if (al->reply) { |
---|
637 | checklist.reply = al->reply; |
---|
638 | HTTPMSGLOCK(checklist.reply); |
---|
639 | } |
---|
640 | |
---|
641 | if (request) { |
---|
642 | al->adapted_request = request; |
---|
643 | HTTPMSGLOCK(al->adapted_request); |
---|
644 | } |
---|
645 | accessLogLog(al, &checklist); |
---|
646 | |
---|
647 | bool updatePerformanceCounters = true; |
---|
648 | if (Config.accessList.stats_collection) { |
---|
649 | ACLFilledChecklist statsCheck(Config.accessList.stats_collection, request, NULL); |
---|
650 | if (al->reply) { |
---|
651 | statsCheck.reply = al->reply; |
---|
652 | HTTPMSGLOCK(statsCheck.reply); |
---|
653 | } |
---|
654 | updatePerformanceCounters = (statsCheck.fastCheck() == ACCESS_ALLOWED); |
---|
655 | } |
---|
656 | |
---|
657 | if (updatePerformanceCounters) { |
---|
658 | if (request) |
---|
659 | updateCounters(); |
---|
660 | |
---|
661 | if (getConn() != NULL && getConn()->clientConnection != NULL) |
---|
662 | clientdbUpdate(getConn()->clientConnection->remote, logType, AnyP::PROTO_HTTP, out.size); |
---|
663 | } |
---|
664 | } |
---|
665 | |
---|
666 | void |
---|
667 | ClientHttpRequest::freeResources() |
---|
668 | { |
---|
669 | safe_free(uri); |
---|
670 | safe_free(log_uri); |
---|
671 | safe_free(redirect.location); |
---|
672 | range_iter.boundary.clean(); |
---|
673 | HTTPMSGUNLOCK(request); |
---|
674 | |
---|
675 | if (client_stream.tail) |
---|
676 | clientStreamAbort((clientStreamNode *)client_stream.tail->data, this); |
---|
677 | } |
---|
678 | |
---|
679 | void |
---|
680 | httpRequestFree(void *data) |
---|
681 | { |
---|
682 | ClientHttpRequest *http = (ClientHttpRequest *)data; |
---|
683 | assert(http != NULL); |
---|
684 | delete http; |
---|
685 | } |
---|
686 | |
---|
687 | bool |
---|
688 | ConnStateData::areAllContextsForThisConnection() const |
---|
689 | { |
---|
690 | ClientSocketContext::Pointer context = getCurrentContext(); |
---|
691 | |
---|
692 | while (context.getRaw()) { |
---|
693 | if (context->http->getConn() != this) |
---|
694 | return false; |
---|
695 | |
---|
696 | context = context->next; |
---|
697 | } |
---|
698 | |
---|
699 | return true; |
---|
700 | } |
---|
701 | |
---|
702 | void |
---|
703 | ConnStateData::freeAllContexts() |
---|
704 | { |
---|
705 | ClientSocketContext::Pointer context; |
---|
706 | |
---|
707 | while ((context = getCurrentContext()).getRaw() != NULL) { |
---|
708 | assert(getCurrentContext() != |
---|
709 | getCurrentContext()->next); |
---|
710 | context->connIsFinished(); |
---|
711 | assert (context != currentobject); |
---|
712 | } |
---|
713 | } |
---|
714 | |
---|
715 | /// propagates abort event to all contexts |
---|
716 | void |
---|
717 | ConnStateData::notifyAllContexts(int xerrno) |
---|
718 | { |
---|
719 | typedef ClientSocketContext::Pointer CSCP; |
---|
720 | for (CSCP c = getCurrentContext(); c.getRaw(); c = c->next) |
---|
721 | c->noteIoError(xerrno); |
---|
722 | } |
---|
723 | |
---|
724 | /* This is a handler normally called by comm_close() */ |
---|
725 | void ConnStateData::connStateClosed(const CommCloseCbParams &io) |
---|
726 | { |
---|
727 | deleteThis("ConnStateData::connStateClosed"); |
---|
728 | } |
---|
729 | |
---|
730 | #if USE_AUTH |
---|
731 | void |
---|
732 | ConnStateData::setAuth(const Auth::UserRequest::Pointer &aur, const char *by) |
---|
733 | { |
---|
734 | if (auth_ == NULL) { |
---|
735 | if (aur != NULL) { |
---|
736 | debugs(33, 2, "Adding connection-auth to " << clientConnection << " from " << by); |
---|
737 | auth_ = aur; |
---|
738 | } |
---|
739 | return; |
---|
740 | } |
---|
741 | |
---|
742 | // clobered with self-pointer |
---|
743 | // NP: something nasty is going on in Squid, but harmless. |
---|
744 | if (aur == auth_) { |
---|
745 | debugs(33, 2, "WARNING: Ignoring duplicate connection-auth for " << clientConnection << " from " << by); |
---|
746 | return; |
---|
747 | } |
---|
748 | |
---|
749 | /* |
---|
750 | * Connection-auth relies on a single set of credentials being preserved |
---|
751 | * for all requests on a connection once they have been setup. |
---|
752 | * There are several things which need to happen to preserve security |
---|
753 | * when connection-auth credentials change unexpectedly or are unset. |
---|
754 | * |
---|
755 | * 1) auth helper released from any active state |
---|
756 | * |
---|
757 | * They can only be reserved by a handshake process which this |
---|
758 | * connection can now never complete. |
---|
759 | * This prevents helpers hanging when their connections close. |
---|
760 | * |
---|
761 | * 2) pinning is expected to be removed and server conn closed |
---|
762 | * |
---|
763 | * The upstream link is authenticated with the same credentials. |
---|
764 | * Expecting the same level of consistency we should have received. |
---|
765 | * This prevents upstream being faced with multiple or missing |
---|
766 | * credentials after authentication. |
---|
767 | * NP: un-pin is left to the cleanup in ConnStateData::swanSong() |
---|
768 | * we just trigger that cleanup here via comm_reset_close() or |
---|
769 | * ConnStateData::stopReceiving() |
---|
770 | * |
---|
771 | * 3) the connection needs to close. |
---|
772 | * |
---|
773 | * This prevents attackers injecting requests into a connection, |
---|
774 | * or gateways wrongly multiplexing users into a single connection. |
---|
775 | * |
---|
776 | * When credentials are missing closure needs to follow an auth |
---|
777 | * challenge for best recovery by the client. |
---|
778 | * |
---|
779 | * When credentials change there is nothing we can do but abort as |
---|
780 | * fast as possible. Sending TCP RST instead of an HTTP response |
---|
781 | * is the best-case action. |
---|
782 | */ |
---|
783 | |
---|
784 | // clobbered with nul-pointer |
---|
785 | if (aur == NULL) { |
---|
786 | debugs(33, 2, "WARNING: Graceful closure on " << clientConnection << " due to connection-auth erase from " << by); |
---|
787 | auth_->releaseAuthServer(); |
---|
788 | auth_ = NULL; |
---|
789 | // XXX: need to test whether the connection re-auth challenge is sent. If not, how to trigger it from here. |
---|
790 | // NP: the current situation seems to fix challenge loops in Safari without visible issues in others. |
---|
791 | // we stop receiving more traffic but can leave the Job running to terminate after the error or challenge is delivered. |
---|
792 | stopReceiving("connection-auth removed"); |
---|
793 | return; |
---|
794 | } |
---|
795 | |
---|
796 | // clobbered with alternative credentials |
---|
797 | if (aur != auth_) { |
---|
798 | debugs(33, 2, "ERROR: Closing " << clientConnection << " due to change of connection-auth from " << by); |
---|
799 | auth_->releaseAuthServer(); |
---|
800 | auth_ = NULL; |
---|
801 | // this is a fatal type of problem. |
---|
802 | // Close the connection immediately with TCP RST to abort all traffic flow |
---|
803 | comm_reset_close(clientConnection); |
---|
804 | return; |
---|
805 | } |
---|
806 | |
---|
807 | /* NOT REACHABLE */ |
---|
808 | } |
---|
809 | #endif |
---|
810 | |
---|
811 | // cleans up before destructor is called |
---|
812 | void |
---|
813 | ConnStateData::swanSong() |
---|
814 | { |
---|
815 | debugs(33, 2, HERE << clientConnection); |
---|
816 | flags.readMore = false; |
---|
817 | DeregisterRunner(this); |
---|
818 | clientdbEstablished(clientConnection->remote, -1); /* decrement */ |
---|
819 | assert(areAllContextsForThisConnection()); |
---|
820 | freeAllContexts(); |
---|
821 | |
---|
822 | unpinConnection(true); |
---|
823 | |
---|
824 | if (Comm::IsConnOpen(clientConnection)) |
---|
825 | clientConnection->close(); |
---|
826 | |
---|
827 | #if USE_AUTH |
---|
828 | // NP: do this bit after closing the connections to avoid side effects from unwanted TCP RST |
---|
829 | setAuth(NULL, "ConnStateData::SwanSong cleanup"); |
---|
830 | #endif |
---|
831 | |
---|
832 | BodyProducer::swanSong(); |
---|
833 | flags.swanSang = true; |
---|
834 | } |
---|
835 | |
---|
836 | bool |
---|
837 | ConnStateData::isOpen() const |
---|
838 | { |
---|
839 | return cbdataReferenceValid(this) && // XXX: checking "this" in a method |
---|
840 | Comm::IsConnOpen(clientConnection) && |
---|
841 | !fd_table[clientConnection->fd].closing(); |
---|
842 | } |
---|
843 | |
---|
844 | ConnStateData::~ConnStateData() |
---|
845 | { |
---|
846 | debugs(33, 3, HERE << clientConnection); |
---|
847 | |
---|
848 | if (isOpen()) |
---|
849 | debugs(33, DBG_IMPORTANT, "BUG: ConnStateData did not close " << clientConnection); |
---|
850 | |
---|
851 | if (!flags.swanSang) |
---|
852 | debugs(33, DBG_IMPORTANT, "BUG: ConnStateData was not destroyed properly; " << clientConnection); |
---|
853 | |
---|
854 | if (bodyPipe != NULL) |
---|
855 | stopProducingFor(bodyPipe, false); |
---|
856 | |
---|
857 | #if USE_OPENSSL |
---|
858 | delete sslServerBump; |
---|
859 | #endif |
---|
860 | } |
---|
861 | |
---|
862 | /** |
---|
863 | * clientSetKeepaliveFlag() sets request->flags.proxyKeepalive. |
---|
864 | * This is the client-side persistent connection flag. We need |
---|
865 | * to set this relatively early in the request processing |
---|
866 | * to handle hacks for broken servers and clients. |
---|
867 | */ |
---|
868 | void |
---|
869 | clientSetKeepaliveFlag(ClientHttpRequest * http) |
---|
870 | { |
---|
871 | HttpRequest *request = http->request; |
---|
872 | |
---|
873 | debugs(33, 3, "http_ver = " << request->http_ver); |
---|
874 | debugs(33, 3, "method = " << request->method); |
---|
875 | |
---|
876 | // TODO: move to HttpRequest::hdrCacheInit, just like HttpReply. |
---|
877 | request->flags.proxyKeepalive = request->persistent(); |
---|
878 | } |
---|
879 | |
---|
880 | /// checks body length of non-chunked requests |
---|
881 | static int |
---|
882 | clientIsContentLengthValid(HttpRequest * r) |
---|
883 | { |
---|
884 | // No Content-Length means this request just has no body, but conflicting |
---|
885 | // Content-Lengths mean a message framing error (RFC 7230 Section 3.3.3 #4). |
---|
886 | if (r->header.conflictingContentLength()) |
---|
887 | return 0; |
---|
888 | |
---|
889 | switch (r->method.id()) { |
---|
890 | |
---|
891 | case Http::METHOD_GET: |
---|
892 | |
---|
893 | case Http::METHOD_HEAD: |
---|
894 | /* We do not want to see a request entity on GET/HEAD requests */ |
---|
895 | return (r->content_length <= 0 || Config.onoff.request_entities); |
---|
896 | |
---|
897 | default: |
---|
898 | /* For other types of requests we don't care */ |
---|
899 | return 1; |
---|
900 | } |
---|
901 | |
---|
902 | /* NOT REACHED */ |
---|
903 | } |
---|
904 | |
---|
905 | int |
---|
906 | clientIsRequestBodyTooLargeForPolicy(int64_t bodyLength) |
---|
907 | { |
---|
908 | if (Config.maxRequestBodySize && |
---|
909 | bodyLength > Config.maxRequestBodySize) |
---|
910 | return 1; /* too large */ |
---|
911 | |
---|
912 | return 0; |
---|
913 | } |
---|
914 | |
---|
915 | // careful: the "current" context may be gone if we wrote an early response |
---|
916 | ClientSocketContext::Pointer |
---|
917 | ConnStateData::getCurrentContext() const |
---|
918 | { |
---|
919 | return currentobject; |
---|
920 | } |
---|
921 | |
---|
922 | void |
---|
923 | ClientSocketContext::deferRecipientForLater(clientStreamNode * node, HttpReply * rep, StoreIOBuffer receivedData) |
---|
924 | { |
---|
925 | debugs(33, 2, "clientSocketRecipient: Deferring request " << http->uri); |
---|
926 | assert(flags.deferred == 0); |
---|
927 | flags.deferred = 1; |
---|
928 | deferredparams.node = node; |
---|
929 | deferredparams.rep = rep; |
---|
930 | deferredparams.queuedBuffer = receivedData; |
---|
931 | return; |
---|
932 | } |
---|
933 | |
---|
934 | bool |
---|
935 | ClientSocketContext::startOfOutput() const |
---|
936 | { |
---|
937 | return http->out.size == 0; |
---|
938 | } |
---|
939 | |
---|
940 | size_t |
---|
941 | ClientSocketContext::lengthToSend(Range<int64_t> const &available) |
---|
942 | { |
---|
943 | /*the size of available range can always fit in a size_t type*/ |
---|
944 | size_t maximum = (size_t)available.size(); |
---|
945 | |
---|
946 | if (!http->request->range) |
---|
947 | return maximum; |
---|
948 | |
---|
949 | assert (canPackMoreRanges()); |
---|
950 | |
---|
951 | if (http->range_iter.debt() == -1) |
---|
952 | return maximum; |
---|
953 | |
---|
954 | assert (http->range_iter.debt() > 0); |
---|
955 | |
---|
956 | /* TODO this + the last line could be a range intersection calculation */ |
---|
957 | if (available.start < http->range_iter.currentSpec()->offset) |
---|
958 | return 0; |
---|
959 | |
---|
960 | return min(http->range_iter.debt(), (int64_t)maximum); |
---|
961 | } |
---|
962 | |
---|
963 | void |
---|
964 | ClientSocketContext::noteSentBodyBytes(size_t bytes) |
---|
965 | { |
---|
966 | debugs(33, 7, bytes << " body bytes"); |
---|
967 | |
---|
968 | http->out.offset += bytes; |
---|
969 | |
---|
970 | if (!http->request->range) |
---|
971 | return; |
---|
972 | |
---|
973 | if (http->range_iter.debt() != -1) { |
---|
974 | http->range_iter.debt(http->range_iter.debt() - bytes); |
---|
975 | assert (http->range_iter.debt() >= 0); |
---|
976 | } |
---|
977 | |
---|
978 | /* debt() always stops at -1, below that is a bug */ |
---|
979 | assert (http->range_iter.debt() >= -1); |
---|
980 | } |
---|
981 | |
---|
982 | bool |
---|
983 | ClientHttpRequest::multipartRangeRequest() const |
---|
984 | { |
---|
985 | return request->multipartRangeRequest(); |
---|
986 | } |
---|
987 | |
---|
988 | bool |
---|
989 | ClientSocketContext::multipartRangeRequest() const |
---|
990 | { |
---|
991 | return http->multipartRangeRequest(); |
---|
992 | } |
---|
993 | |
---|
994 | void |
---|
995 | ClientSocketContext::sendBody(HttpReply * rep, StoreIOBuffer bodyData) |
---|
996 | { |
---|
997 | assert(rep == NULL); |
---|
998 | |
---|
999 | if (!multipartRangeRequest() && !http->request->flags.chunkedReply) { |
---|
1000 | size_t length = lengthToSend(bodyData.range()); |
---|
1001 | noteSentBodyBytes (length); |
---|
1002 | AsyncCall::Pointer call = commCbCall(33, 5, "clientWriteBodyComplete", |
---|
1003 | CommIoCbPtrFun(clientWriteBodyComplete, this)); |
---|
1004 | Comm::Write(clientConnection, bodyData.data, length, call, NULL); |
---|
1005 | return; |
---|
1006 | } |
---|
1007 | |
---|
1008 | MemBuf mb; |
---|
1009 | mb.init(); |
---|
1010 | if (multipartRangeRequest()) |
---|
1011 | packRange(bodyData, &mb); |
---|
1012 | else |
---|
1013 | packChunk(bodyData, mb); |
---|
1014 | |
---|
1015 | if (mb.contentSize()) { |
---|
1016 | /* write */ |
---|
1017 | AsyncCall::Pointer call = commCbCall(33, 5, "clientWriteComplete", |
---|
1018 | CommIoCbPtrFun(clientWriteComplete, this)); |
---|
1019 | Comm::Write(clientConnection, &mb, call); |
---|
1020 | } else |
---|
1021 | writeComplete(clientConnection, NULL, 0, Comm::OK); |
---|
1022 | } |
---|
1023 | |
---|
1024 | /** |
---|
1025 | * Packs bodyData into mb using chunked encoding. Packs the last-chunk |
---|
1026 | * if bodyData is empty. |
---|
1027 | */ |
---|
1028 | void |
---|
1029 | ClientSocketContext::packChunk(const StoreIOBuffer &bodyData, MemBuf &mb) |
---|
1030 | { |
---|
1031 | const uint64_t length = |
---|
1032 | static_cast<uint64_t>(lengthToSend(bodyData.range())); |
---|
1033 | noteSentBodyBytes(length); |
---|
1034 | |
---|
1035 | mb.Printf("%" PRIX64 "\r\n", length); |
---|
1036 | mb.append(bodyData.data, length); |
---|
1037 | mb.Printf("\r\n"); |
---|
1038 | } |
---|
1039 | |
---|
1040 | /** put terminating boundary for multiparts */ |
---|
1041 | static void |
---|
1042 | clientPackTermBound(String boundary, MemBuf * mb) |
---|
1043 | { |
---|
1044 | mb->Printf("\r\n--" SQUIDSTRINGPH "--\r\n", SQUIDSTRINGPRINT(boundary)); |
---|
1045 | debugs(33, 6, "clientPackTermBound: buf offset: " << mb->size); |
---|
1046 | } |
---|
1047 | |
---|
1048 | /** appends a "part" HTTP header (as in a multi-part/range reply) to the buffer */ |
---|
1049 | static void |
---|
1050 | clientPackRangeHdr(const HttpReply * rep, const HttpHdrRangeSpec * spec, String boundary, MemBuf * mb) |
---|
1051 | { |
---|
1052 | HttpHeader hdr(hoReply); |
---|
1053 | Packer p; |
---|
1054 | assert(rep); |
---|
1055 | assert(spec); |
---|
1056 | |
---|
1057 | /* put boundary */ |
---|
1058 | debugs(33, 5, "clientPackRangeHdr: appending boundary: " << boundary); |
---|
1059 | /* rfc2046 requires to _prepend_ boundary with <crlf>! */ |
---|
1060 | mb->Printf("\r\n--" SQUIDSTRINGPH "\r\n", SQUIDSTRINGPRINT(boundary)); |
---|
1061 | |
---|
1062 | /* stuff the header with required entries and pack it */ |
---|
1063 | |
---|
1064 | if (rep->header.has(HDR_CONTENT_TYPE)) |
---|
1065 | hdr.putStr(HDR_CONTENT_TYPE, rep->header.getStr(HDR_CONTENT_TYPE)); |
---|
1066 | |
---|
1067 | httpHeaderAddContRange(&hdr, *spec, rep->content_length); |
---|
1068 | |
---|
1069 | packerToMemInit(&p, mb); |
---|
1070 | |
---|
1071 | hdr.packInto(&p); |
---|
1072 | |
---|
1073 | packerClean(&p); |
---|
1074 | |
---|
1075 | hdr.clean(); |
---|
1076 | |
---|
1077 | /* append <crlf> (we packed a header, not a reply) */ |
---|
1078 | mb->Printf("\r\n"); |
---|
1079 | } |
---|
1080 | |
---|
1081 | /** |
---|
1082 | * extracts a "range" from *buf and appends them to mb, updating |
---|
1083 | * all offsets and such. |
---|
1084 | */ |
---|
1085 | void |
---|
1086 | ClientSocketContext::packRange(StoreIOBuffer const &source, MemBuf * mb) |
---|
1087 | { |
---|
1088 | HttpHdrRangeIter * i = &http->range_iter; |
---|
1089 | Range<int64_t> available (source.range()); |
---|
1090 | char const *buf = source.data; |
---|
1091 | |
---|
1092 | while (i->currentSpec() && available.size()) { |
---|
1093 | const size_t copy_sz = lengthToSend(available); |
---|
1094 | |
---|
1095 | if (copy_sz) { |
---|
1096 | /* |
---|
1097 | * intersection of "have" and "need" ranges must not be empty |
---|
1098 | */ |
---|
1099 | assert(http->out.offset < i->currentSpec()->offset + i->currentSpec()->length); |
---|
1100 | assert(http->out.offset + (int64_t)available.size() > i->currentSpec()->offset); |
---|
1101 | |
---|
1102 | /* |
---|
1103 | * put boundary and headers at the beginning of a range in a |
---|
1104 | * multi-range |
---|
1105 | */ |
---|
1106 | |
---|
1107 | if (http->multipartRangeRequest() && i->debt() == i->currentSpec()->length) { |
---|
1108 | assert(http->memObject()); |
---|
1109 | clientPackRangeHdr( |
---|
1110 | http->memObject()->getReply(), /* original reply */ |
---|
1111 | i->currentSpec(), /* current range */ |
---|
1112 | i->boundary, /* boundary, the same for all */ |
---|
1113 | mb); |
---|
1114 | } |
---|
1115 | |
---|
1116 | /* |
---|
1117 | * append content |
---|
1118 | */ |
---|
1119 | debugs(33, 3, "clientPackRange: appending " << copy_sz << " bytes"); |
---|
1120 | |
---|
1121 | noteSentBodyBytes (copy_sz); |
---|
1122 | |
---|
1123 | mb->append(buf, copy_sz); |
---|
1124 | |
---|
1125 | /* |
---|
1126 | * update offsets |
---|
1127 | */ |
---|
1128 | available.start += copy_sz; |
---|
1129 | |
---|
1130 | buf += copy_sz; |
---|
1131 | |
---|
1132 | } |
---|
1133 | |
---|
1134 | if (!canPackMoreRanges()) { |
---|
1135 | debugs(33, 3, "clientPackRange: Returning because !canPackMoreRanges."); |
---|
1136 | |
---|
1137 | if (i->debt() == 0) |
---|
1138 | /* put terminating boundary for multiparts */ |
---|
1139 | clientPackTermBound(i->boundary, mb); |
---|
1140 | |
---|
1141 | return; |
---|
1142 | } |
---|
1143 | |
---|
1144 | int64_t nextOffset = getNextRangeOffset(); |
---|
1145 | |
---|
1146 | assert (nextOffset >= http->out.offset); |
---|
1147 | |
---|
1148 | int64_t skip = nextOffset - http->out.offset; |
---|
1149 | |
---|
1150 | /* adjust for not to be transmitted bytes */ |
---|
1151 | http->out.offset = nextOffset; |
---|
1152 | |
---|
1153 | if (available.size() <= (uint64_t)skip) |
---|
1154 | return; |
---|
1155 | |
---|
1156 | available.start += skip; |
---|
1157 | |
---|
1158 | buf += skip; |
---|
1159 | |
---|
1160 | if (copy_sz == 0) |
---|
1161 | return; |
---|
1162 | } |
---|
1163 | } |
---|
1164 | |
---|
1165 | /** returns expected content length for multi-range replies |
---|
1166 | * note: assumes that httpHdrRangeCanonize has already been called |
---|
1167 | * warning: assumes that HTTP headers for individual ranges at the |
---|
1168 | * time of the actuall assembly will be exactly the same as |
---|
1169 | * the headers when clientMRangeCLen() is called */ |
---|
1170 | int |
---|
1171 | ClientHttpRequest::mRangeCLen() |
---|
1172 | { |
---|
1173 | int64_t clen = 0; |
---|
1174 | MemBuf mb; |
---|
1175 | |
---|
1176 | assert(memObject()); |
---|
1177 | |
---|
1178 | mb.init(); |
---|
1179 | HttpHdrRange::iterator pos = request->range->begin(); |
---|
1180 | |
---|
1181 | while (pos != request->range->end()) { |
---|
1182 | /* account for headers for this range */ |
---|
1183 | mb.reset(); |
---|
1184 | clientPackRangeHdr(memObject()->getReply(), |
---|
1185 | *pos, range_iter.boundary, &mb); |
---|
1186 | clen += mb.size; |
---|
1187 | |
---|
1188 | /* account for range content */ |
---|
1189 | clen += (*pos)->length; |
---|
1190 | |
---|
1191 | debugs(33, 6, "clientMRangeCLen: (clen += " << mb.size << " + " << (*pos)->length << ") == " << clen); |
---|
1192 | ++pos; |
---|
1193 | } |
---|
1194 | |
---|
1195 | /* account for the terminating boundary */ |
---|
1196 | mb.reset(); |
---|
1197 | |
---|
1198 | clientPackTermBound(range_iter.boundary, &mb); |
---|
1199 | |
---|
1200 | clen += mb.size; |
---|
1201 | |
---|
1202 | mb.clean(); |
---|
1203 | |
---|
1204 | return clen; |
---|
1205 | } |
---|
1206 | |
---|
1207 | /** |
---|
1208 | * returns true if If-Range specs match reply, false otherwise |
---|
1209 | */ |
---|
1210 | static int |
---|
1211 | clientIfRangeMatch(ClientHttpRequest * http, HttpReply * rep) |
---|
1212 | { |
---|
1213 | const TimeOrTag spec = http->request->header.getTimeOrTag(HDR_IF_RANGE); |
---|
1214 | /* check for parsing falure */ |
---|
1215 | |
---|
1216 | if (!spec.valid) |
---|
1217 | return 0; |
---|
1218 | |
---|
1219 | /* got an ETag? */ |
---|
1220 | if (spec.tag.str) { |
---|
1221 | ETag rep_tag = rep->header.getETag(HDR_ETAG); |
---|
1222 | debugs(33, 3, "clientIfRangeMatch: ETags: " << spec.tag.str << " and " << |
---|
1223 | (rep_tag.str ? rep_tag.str : "<none>")); |
---|
1224 | |
---|
1225 | if (!rep_tag.str) |
---|
1226 | return 0; /* entity has no etag to compare with! */ |
---|
1227 | |
---|
1228 | if (spec.tag.weak || rep_tag.weak) { |
---|
1229 | debugs(33, DBG_IMPORTANT, "clientIfRangeMatch: Weak ETags are not allowed in If-Range: " << spec.tag.str << " ? " << rep_tag.str); |
---|
1230 | return 0; /* must use strong validator for sub-range requests */ |
---|
1231 | } |
---|
1232 | |
---|
1233 | return etagIsStrongEqual(rep_tag, spec.tag); |
---|
1234 | } |
---|
1235 | |
---|
1236 | /* got modification time? */ |
---|
1237 | if (spec.time >= 0) { |
---|
1238 | return http->storeEntry()->lastmod <= spec.time; |
---|
1239 | } |
---|
1240 | |
---|
1241 | assert(0); /* should not happen */ |
---|
1242 | return 0; |
---|
1243 | } |
---|
1244 | |
---|
1245 | /** |
---|
1246 | * generates a "unique" boundary string for multipart responses |
---|
1247 | * the caller is responsible for cleaning the string */ |
---|
1248 | String |
---|
1249 | ClientHttpRequest::rangeBoundaryStr() const |
---|
1250 | { |
---|
1251 | const char *key; |
---|
1252 | String b(APP_FULLNAME); |
---|
1253 | b.append(":",1); |
---|
1254 | key = storeEntry()->getMD5Text(); |
---|
1255 | b.append(key, strlen(key)); |
---|
1256 | return b; |
---|
1257 | } |
---|
1258 | |
---|
1259 | /** adds appropriate Range headers if needed */ |
---|
1260 | void |
---|
1261 | ClientSocketContext::buildRangeHeader(HttpReply * rep) |
---|
1262 | { |
---|
1263 | HttpHeader *hdr = rep ? &rep->header : 0; |
---|
1264 | const char *range_err = NULL; |
---|
1265 | HttpRequest *request = http->request; |
---|
1266 | assert(request->range); |
---|
1267 | /* check if we still want to do ranges */ |
---|
1268 | |
---|
1269 | int64_t roffLimit = request->getRangeOffsetLimit(); |
---|
1270 | |
---|
1271 | if (!rep) |
---|
1272 | range_err = "no [parse-able] reply"; |
---|
1273 | else if ((rep->sline.status() != Http::scOkay) && (rep->sline.status() != Http::scPartialContent)) |
---|
1274 | range_err = "wrong status code"; |
---|
1275 | else if (hdr->has(HDR_CONTENT_RANGE)) |
---|
1276 | range_err = "origin server does ranges"; |
---|
1277 | else if (rep->content_length < 0) |
---|
1278 | range_err = "unknown length"; |
---|
1279 | else if (rep->content_length != http->memObject()->getReply()->content_length) |
---|
1280 | range_err = "INCONSISTENT length"; /* a bug? */ |
---|
1281 | |
---|
1282 | /* hits only - upstream CachePeer determines correct behaviour on misses, and client_side_reply determines |
---|
1283 | * hits candidates |
---|
1284 | */ |
---|
1285 | else if (logTypeIsATcpHit(http->logType) && http->request->header.has(HDR_IF_RANGE) && !clientIfRangeMatch(http, rep)) |
---|
1286 | range_err = "If-Range match failed"; |
---|
1287 | else if (!http->request->range->canonize(rep)) |
---|
1288 | range_err = "canonization failed"; |
---|
1289 | else if (http->request->range->isComplex()) |
---|
1290 | range_err = "too complex range header"; |
---|
1291 | else if (!logTypeIsATcpHit(http->logType) && http->request->range->offsetLimitExceeded(roffLimit)) |
---|
1292 | range_err = "range outside range_offset_limit"; |
---|
1293 | |
---|
1294 | /* get rid of our range specs on error */ |
---|
1295 | if (range_err) { |
---|
1296 | /* XXX We do this here because we need canonisation etc. However, this current |
---|
1297 | * code will lead to incorrect store offset requests - the store will have the |
---|
1298 | * offset data, but we won't be requesting it. |
---|
1299 | * So, we can either re-request, or generate an error |
---|
1300 | */ |
---|
1301 | http->request->ignoreRange(range_err); |
---|
1302 | } else { |
---|
1303 | /* XXX: TODO: Review, this unconditional set may be wrong. */ |
---|
1304 | rep->sline.set(rep->sline.version, Http::scPartialContent); |
---|
1305 | // web server responded with a valid, but unexpected range. |
---|
1306 | // will (try-to) forward as-is. |
---|
1307 | //TODO: we should cope with multirange request/responses |
---|
1308 | bool replyMatchRequest = rep->content_range != NULL ? |
---|
1309 | request->range->contains(rep->content_range->spec) : |
---|
1310 | true; |
---|
1311 | const int spec_count = http->request->range->specs.size(); |
---|
1312 | int64_t actual_clen = -1; |
---|
1313 | |
---|
1314 | debugs(33, 3, "clientBuildRangeHeader: range spec count: " << |
---|
1315 | spec_count << " virgin clen: " << rep->content_length); |
---|
1316 | assert(spec_count > 0); |
---|
1317 | /* append appropriate header(s) */ |
---|
1318 | |
---|
1319 | if (spec_count == 1) { |
---|
1320 | if (!replyMatchRequest) { |
---|
1321 | hdr->delById(HDR_CONTENT_RANGE); |
---|
1322 | hdr->putContRange(rep->content_range); |
---|
1323 | actual_clen = rep->content_length; |
---|
1324 | //http->range_iter.pos = rep->content_range->spec.begin(); |
---|
1325 | (*http->range_iter.pos)->offset = rep->content_range->spec.offset; |
---|
1326 | (*http->range_iter.pos)->length = rep->content_range->spec.length; |
---|
1327 | |
---|
1328 | } else { |
---|
1329 | HttpHdrRange::iterator pos = http->request->range->begin(); |
---|
1330 | assert(*pos); |
---|
1331 | /* append Content-Range */ |
---|
1332 | |
---|
1333 | if (!hdr->has(HDR_CONTENT_RANGE)) { |
---|
1334 | /* No content range, so this was a full object we are |
---|
1335 | * sending parts of. |
---|
1336 | */ |
---|
1337 | httpHeaderAddContRange(hdr, **pos, rep->content_length); |
---|
1338 | } |
---|
1339 | |
---|
1340 | /* set new Content-Length to the actual number of bytes |
---|
1341 | * transmitted in the message-body */ |
---|
1342 | actual_clen = (*pos)->length; |
---|
1343 | } |
---|
1344 | } else { |
---|
1345 | /* multipart! */ |
---|
1346 | /* generate boundary string */ |
---|
1347 | http->range_iter.boundary = http->rangeBoundaryStr(); |
---|
1348 | /* delete old Content-Type, add ours */ |
---|
1349 | hdr->delById(HDR_CONTENT_TYPE); |
---|
1350 | httpHeaderPutStrf(hdr, HDR_CONTENT_TYPE, |
---|
1351 | "multipart/byteranges; boundary=\"" SQUIDSTRINGPH "\"", |
---|
1352 | SQUIDSTRINGPRINT(http->range_iter.boundary)); |
---|
1353 | /* Content-Length is not required in multipart responses |
---|
1354 | * but it is always nice to have one */ |
---|
1355 | actual_clen = http->mRangeCLen(); |
---|
1356 | /* http->out needs to start where we want data at */ |
---|
1357 | http->out.offset = http->range_iter.currentSpec()->offset; |
---|
1358 | } |
---|
1359 | |
---|
1360 | /* replace Content-Length header */ |
---|
1361 | assert(actual_clen >= 0); |
---|
1362 | |
---|
1363 | hdr->delById(HDR_CONTENT_LENGTH); |
---|
1364 | |
---|
1365 | hdr->putInt64(HDR_CONTENT_LENGTH, actual_clen); |
---|
1366 | |
---|
1367 | debugs(33, 3, "clientBuildRangeHeader: actual content length: " << actual_clen); |
---|
1368 | |
---|
1369 | /* And start the range iter off */ |
---|
1370 | http->range_iter.updateSpec(); |
---|
1371 | } |
---|
1372 | } |
---|
1373 | |
---|
1374 | void |
---|
1375 | ClientSocketContext::prepareReply(HttpReply * rep) |
---|
1376 | { |
---|
1377 | reply = rep; |
---|
1378 | |
---|
1379 | if (http->request->range) |
---|
1380 | buildRangeHeader(rep); |
---|
1381 | } |
---|
1382 | |
---|
1383 | void |
---|
1384 | ClientSocketContext::sendStartOfMessage(HttpReply * rep, StoreIOBuffer bodyData) |
---|
1385 | { |
---|
1386 | prepareReply(rep); |
---|
1387 | assert (rep); |
---|
1388 | MemBuf *mb = rep->pack(); |
---|
1389 | |
---|
1390 | // dump now, so we dont output any body. |
---|
1391 | debugs(11, 2, "HTTP Client " << clientConnection); |
---|
1392 | debugs(11, 2, "HTTP Client REPLY:\n---------\n" << mb->buf << "\n----------"); |
---|
1393 | |
---|
1394 | /* Save length of headers for persistent conn checks */ |
---|
1395 | http->out.headers_sz = mb->contentSize(); |
---|
1396 | #if HEADERS_LOG |
---|
1397 | |
---|
1398 | headersLog(0, 0, http->request->method, rep); |
---|
1399 | #endif |
---|
1400 | |
---|
1401 | if (bodyData.data && bodyData.length) { |
---|
1402 | if (multipartRangeRequest()) |
---|
1403 | packRange(bodyData, mb); |
---|
1404 | else if (http->request->flags.chunkedReply) { |
---|
1405 | packChunk(bodyData, *mb); |
---|
1406 | } else { |
---|
1407 | size_t length = lengthToSend(bodyData.range()); |
---|
1408 | noteSentBodyBytes (length); |
---|
1409 | |
---|
1410 | mb->append(bodyData.data, length); |
---|
1411 | } |
---|
1412 | } |
---|
1413 | |
---|
1414 | /* write */ |
---|
1415 | debugs(33,7, HERE << "sendStartOfMessage schedules clientWriteComplete"); |
---|
1416 | AsyncCall::Pointer call = commCbCall(33, 5, "clientWriteComplete", |
---|
1417 | CommIoCbPtrFun(clientWriteComplete, this)); |
---|
1418 | Comm::Write(clientConnection, mb, call); |
---|
1419 | delete mb; |
---|
1420 | } |
---|
1421 | |
---|
1422 | /** |
---|
1423 | * Write a chunk of data to a client socket. If the reply is present, |
---|
1424 | * send the reply headers down the wire too, and clean them up when |
---|
1425 | * finished. |
---|
1426 | * Pre-condition: |
---|
1427 | * The request is one backed by a connection, not an internal request. |
---|
1428 | * data context is not NULL |
---|
1429 | * There are no more entries in the stream chain. |
---|
1430 | */ |
---|
1431 | void |
---|
1432 | clientSocketRecipient(clientStreamNode * node, ClientHttpRequest * http, |
---|
1433 | HttpReply * rep, StoreIOBuffer receivedData) |
---|
1434 | { |
---|
1435 | // dont tryt to deliver if client already ABORTED |
---|
1436 | if (!http->getConn() || !cbdataReferenceValid(http->getConn()) || !Comm::IsConnOpen(http->getConn()->clientConnection)) |
---|
1437 | return; |
---|
1438 | |
---|
1439 | /* Test preconditions */ |
---|
1440 | assert(node != NULL); |
---|
1441 | PROF_start(clientSocketRecipient); |
---|
1442 | /* TODO: handle this rather than asserting |
---|
1443 | * - it should only ever happen if we cause an abort and |
---|
1444 | * the callback chain loops back to here, so we can simply return. |
---|
1445 | * However, that itself shouldn't happen, so it stays as an assert for now. |
---|
1446 | */ |
---|
1447 | assert(cbdataReferenceValid(node)); |
---|
1448 | assert(node->node.next == NULL); |
---|
1449 | ClientSocketContext::Pointer context = dynamic_cast<ClientSocketContext *>(node->data.getRaw()); |
---|
1450 | assert(context != NULL); |
---|
1451 | |
---|
1452 | /* TODO: check offset is what we asked for */ |
---|
1453 | |
---|
1454 | if (context != http->getConn()->getCurrentContext()) |
---|
1455 | context->deferRecipientForLater(node, rep, receivedData); |
---|
1456 | else |
---|
1457 | http->getConn()->handleReply(rep, receivedData); |
---|
1458 | |
---|
1459 | PROF_stop(clientSocketRecipient); |
---|
1460 | } |
---|
1461 | |
---|
1462 | /** |
---|
1463 | * Called when a downstream node is no longer interested in |
---|
1464 | * our data. As we are a terminal node, this means on aborts |
---|
1465 | * only |
---|
1466 | */ |
---|
1467 | void |
---|
1468 | clientSocketDetach(clientStreamNode * node, ClientHttpRequest * http) |
---|
1469 | { |
---|
1470 | /* Test preconditions */ |
---|
1471 | assert(node != NULL); |
---|
1472 | /* TODO: handle this rather than asserting |
---|
1473 | * - it should only ever happen if we cause an abort and |
---|
1474 | * the callback chain loops back to here, so we can simply return. |
---|
1475 | * However, that itself shouldn't happen, so it stays as an assert for now. |
---|
1476 | */ |
---|
1477 | assert(cbdataReferenceValid(node)); |
---|
1478 | /* Set null by ContextFree */ |
---|
1479 | assert(node->node.next == NULL); |
---|
1480 | /* this is the assert discussed above */ |
---|
1481 | assert(NULL == dynamic_cast<ClientSocketContext *>(node->data.getRaw())); |
---|
1482 | /* We are only called when the client socket shutsdown. |
---|
1483 | * Tell the prev pipeline member we're finished |
---|
1484 | */ |
---|
1485 | clientStreamDetach(node, http); |
---|
1486 | } |
---|
1487 | |
---|
1488 | static void |
---|
1489 | clientWriteBodyComplete(const Comm::ConnectionPointer &conn, char *buf, size_t size, Comm::Flag errflag, int xerrno, void *data) |
---|
1490 | { |
---|
1491 | debugs(33,7, HERE << "clientWriteBodyComplete schedules clientWriteComplete"); |
---|
1492 | clientWriteComplete(conn, NULL, size, errflag, xerrno, data); |
---|
1493 | } |
---|
1494 | |
---|
1495 | void |
---|
1496 | ConnStateData::readNextRequest() |
---|
1497 | { |
---|
1498 | debugs(33, 5, HERE << clientConnection << " reading next req"); |
---|
1499 | |
---|
1500 | fd_note(clientConnection->fd, "Idle client: Waiting for next request"); |
---|
1501 | /** |
---|
1502 | * Set the timeout BEFORE calling readSomeData(). |
---|
1503 | */ |
---|
1504 | typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer; |
---|
1505 | AsyncCall::Pointer timeoutCall = JobCallback(33, 5, |
---|
1506 | TimeoutDialer, this, ConnStateData::requestTimeout); |
---|
1507 | commSetConnTimeout(clientConnection, idleTimeout(), timeoutCall); |
---|
1508 | |
---|
1509 | readSomeData(); |
---|
1510 | /** Please don't do anything with the FD past here! */ |
---|
1511 | } |
---|
1512 | |
---|
1513 | static void |
---|
1514 | ClientSocketContextPushDeferredIfNeeded(ClientSocketContext::Pointer deferredRequest, ConnStateData * conn) |
---|
1515 | { |
---|
1516 | debugs(33, 2, HERE << conn->clientConnection << " Sending next"); |
---|
1517 | |
---|
1518 | /** If the client stream is waiting on a socket write to occur, then */ |
---|
1519 | |
---|
1520 | if (deferredRequest->flags.deferred) { |
---|
1521 | /** NO data is allowed to have been sent. */ |
---|
1522 | assert(deferredRequest->http->out.size == 0); |
---|
1523 | /** defer now. */ |
---|
1524 | clientSocketRecipient(deferredRequest->deferredparams.node, |
---|
1525 | deferredRequest->http, |
---|
1526 | deferredRequest->deferredparams.rep, |
---|
1527 | deferredRequest->deferredparams.queuedBuffer); |
---|
1528 | } |
---|
1529 | |
---|
1530 | /** otherwise, the request is still active in a callbacksomewhere, |
---|
1531 | * and we are done |
---|
1532 | */ |
---|
1533 | } |
---|
1534 | |
---|
1535 | /// called when we have successfully finished writing the response |
---|
1536 | void |
---|
1537 | ClientSocketContext::keepaliveNextRequest() |
---|
1538 | { |
---|
1539 | ConnStateData * conn = http->getConn(); |
---|
1540 | |
---|
1541 | debugs(33, 3, HERE << "ConnnStateData(" << conn->clientConnection << "), Context(" << clientConnection << ")"); |
---|
1542 | connIsFinished(); |
---|
1543 | |
---|
1544 | if (conn->pinning.pinned && !Comm::IsConnOpen(conn->pinning.serverConnection)) { |
---|
1545 | debugs(33, 2, HERE << conn->clientConnection << " Connection was pinned but server side gone. Terminating client connection"); |
---|
1546 | conn->clientConnection->close(); |
---|
1547 | return; |
---|
1548 | } |
---|
1549 | |
---|
1550 | /** \par |
---|
1551 | * We are done with the response, and we are either still receiving request |
---|
1552 | * body (early response!) or have already stopped receiving anything. |
---|
1553 | * |
---|
1554 | * If we are still receiving, then clientParseRequest() below will fail. |
---|
1555 | * (XXX: but then we will call readNextRequest() which may succeed and |
---|
1556 | * execute a smuggled request as we are not done with the current request). |
---|
1557 | * |
---|
1558 | * If we stopped because we got everything, then try the next request. |
---|
1559 | * |
---|
1560 | * If we stopped receiving because of an error, then close now to avoid |
---|
1561 | * getting stuck and to prevent accidental request smuggling. |
---|
1562 | */ |
---|
1563 | |
---|
1564 | if (const char *reason = conn->stoppedReceiving()) { |
---|
1565 | debugs(33, 3, HERE << "closing for earlier request error: " << reason); |
---|
1566 | conn->clientConnection->close(); |
---|
1567 | return; |
---|
1568 | } |
---|
1569 | |
---|
1570 | /** \par |
---|
1571 | * Attempt to parse a request from the request buffer. |
---|
1572 | * If we've been fed a pipelined request it may already |
---|
1573 | * be in our read buffer. |
---|
1574 | * |
---|
1575 | \par |
---|
1576 | * This needs to fall through - if we're unlucky and parse the _last_ request |
---|
1577 | * from our read buffer we may never re-register for another client read. |
---|
1578 | */ |
---|
1579 | |
---|
1580 | if (conn->clientParseRequests()) { |
---|
1581 | debugs(33, 3, HERE << conn->clientConnection << ": parsed next request from buffer"); |
---|
1582 | } |
---|
1583 | |
---|
1584 | /** \par |
---|
1585 | * Either we need to kick-start another read or, if we have |
---|
1586 | * a half-closed connection, kill it after the last request. |
---|
1587 | * This saves waiting for half-closed connections to finished being |
---|
1588 | * half-closed _AND_ then, sometimes, spending "Timeout" time in |
---|
1589 | * the keepalive "Waiting for next request" state. |
---|
1590 | */ |
---|
1591 | if (commIsHalfClosed(conn->clientConnection->fd) && (conn->getConcurrentRequestCount() == 0)) { |
---|
1592 | debugs(33, 3, "ClientSocketContext::keepaliveNextRequest: half-closed client with no pending requests, closing"); |
---|
1593 | conn->clientConnection->close(); |
---|
1594 | return; |
---|
1595 | } |
---|
1596 | |
---|
1597 | ClientSocketContext::Pointer deferredRequest; |
---|
1598 | |
---|
1599 | /** \par |
---|
1600 | * At this point we either have a parsed request (which we've |
---|
1601 | * kicked off the processing for) or not. If we have a deferred |
---|
1602 | * request (parsed but deferred for pipeling processing reasons) |
---|
1603 | * then look at processing it. If not, simply kickstart |
---|
1604 | * another read. |
---|
1605 | */ |
---|
1606 | |
---|
1607 | if ((deferredRequest = conn->getCurrentContext()).getRaw()) { |
---|
1608 | debugs(33, 3, HERE << conn->clientConnection << ": calling PushDeferredIfNeeded"); |
---|
1609 | ClientSocketContextPushDeferredIfNeeded(deferredRequest, conn); |
---|
1610 | } else if (conn->flags.readMore) { |
---|
1611 | debugs(33, 3, HERE << conn->clientConnection << ": calling conn->readNextRequest()"); |
---|
1612 | conn->readNextRequest(); |
---|
1613 | } else { |
---|
1614 | // XXX: Can this happen? CONNECT tunnels have deferredRequest set. |
---|
1615 | debugs(33, DBG_IMPORTANT, HERE << "abandoning " << conn->clientConnection); |
---|
1616 | } |
---|
1617 | } |
---|
1618 | |
---|
1619 | void |
---|
1620 | clientUpdateSocketStats(LogTags logType, size_t size) |
---|
1621 | { |
---|
1622 | if (size == 0) |
---|
1623 | return; |
---|
1624 | |
---|
1625 | kb_incr(&statCounter.client_http.kbytes_out, size); |
---|
1626 | |
---|
1627 | if (logTypeIsATcpHit(logType)) |
---|
1628 | kb_incr(&statCounter.client_http.hit_kbytes_out, size); |
---|
1629 | } |
---|
1630 | |
---|
1631 | /** |
---|
1632 | * increments iterator "i" |
---|
1633 | * used by clientPackMoreRanges |
---|
1634 | * |
---|
1635 | \retval true there is still data available to pack more ranges |
---|
1636 | \retval false |
---|
1637 | */ |
---|
1638 | bool |
---|
1639 | ClientSocketContext::canPackMoreRanges() const |
---|
1640 | { |
---|
1641 | /** first update iterator "i" if needed */ |
---|
1642 | |
---|
1643 | if (!http->range_iter.debt()) { |
---|
1644 | debugs(33, 5, HERE << "At end of current range spec for " << clientConnection); |
---|
1645 | |
---|
1646 | if (http->range_iter.pos != http->range_iter.end) |
---|
1647 | ++http->range_iter.pos; |
---|
1648 | |
---|
1649 | http->range_iter.updateSpec(); |
---|
1650 | } |
---|
1651 | |
---|
1652 | assert(!http->range_iter.debt() == !http->range_iter.currentSpec()); |
---|
1653 | |
---|
1654 | /* paranoid sync condition */ |
---|
1655 | /* continue condition: need_more_data */ |
---|
1656 | debugs(33, 5, "ClientSocketContext::canPackMoreRanges: returning " << (http->range_iter.currentSpec() ? true : false)); |
---|
1657 | return http->range_iter.currentSpec() ? true : false; |
---|
1658 | } |
---|
1659 | |
---|
1660 | int64_t |
---|
1661 | ClientSocketContext::getNextRangeOffset() const |
---|
1662 | { |
---|
1663 | debugs (33, 5, "range: " << http->request->range << |
---|
1664 | "; http offset " << http->out.offset << |
---|
1665 | "; reply " << reply); |
---|
1666 | |
---|
1667 | // XXX: This method is called from many places, including pullData() which |
---|
1668 | // may be called before prepareReply() [on some Squid-generated errors]. |
---|
1669 | // Hence, we may not even know yet whether we should honor/do ranges. |
---|
1670 | |
---|
1671 | if (http->request->range) { |
---|
1672 | /* offset in range specs does not count the prefix of an http msg */ |
---|
1673 | /* check: reply was parsed and range iterator was initialized */ |
---|
1674 | assert(http->range_iter.valid); |
---|
1675 | /* filter out data according to range specs */ |
---|
1676 | assert (canPackMoreRanges()); |
---|
1677 | { |
---|
1678 | int64_t start; /* offset of still missing data */ |
---|
1679 | assert(http->range_iter.currentSpec()); |
---|
1680 | start = http->range_iter.currentSpec()->offset + http->range_iter.currentSpec()->length - http->range_iter.debt(); |
---|
1681 | debugs(33, 3, "clientPackMoreRanges: in: offset: " << http->out.offset); |
---|
1682 | debugs(33, 3, "clientPackMoreRanges: out:" |
---|
1683 | " start: " << start << |
---|
1684 | " spec[" << http->range_iter.pos - http->request->range->begin() << "]:" << |
---|
1685 | " [" << http->range_iter.currentSpec()->offset << |
---|
1686 | ", " << http->range_iter.currentSpec()->offset + http->range_iter.currentSpec()->length << ")," |
---|
1687 | " len: " << http->range_iter.currentSpec()->length << |
---|
1688 | " debt: " << http->range_iter.debt()); |
---|
1689 | if (http->range_iter.currentSpec()->length != -1) |
---|
1690 | assert(http->out.offset <= start); /* we did not miss it */ |
---|
1691 | |
---|
1692 | return start; |
---|
1693 | } |
---|
1694 | |
---|
1695 | } else if (reply && reply->content_range) { |
---|
1696 | /* request does not have ranges, but reply does */ |
---|
1697 | /** \todo FIXME: should use range_iter_pos on reply, as soon as reply->content_range |
---|
1698 | * becomes HttpHdrRange rather than HttpHdrRangeSpec. |
---|
1699 | */ |
---|
1700 | return http->out.offset + reply->content_range->spec.offset; |
---|
1701 | } |
---|
1702 | |
---|
1703 | return http->out.offset; |
---|
1704 | } |
---|
1705 | |
---|
1706 | void |
---|
1707 | ClientSocketContext::pullData() |
---|
1708 | { |
---|
1709 | debugs(33, 5, reply << " written " << http->out.size << " into " << clientConnection); |
---|
1710 | |
---|
1711 | /* More data will be coming from the stream. */ |
---|
1712 | StoreIOBuffer readBuffer; |
---|
1713 | /* XXX: Next requested byte in the range sequence */ |
---|
1714 | /* XXX: length = getmaximumrangelenfgth */ |
---|
1715 | readBuffer.offset = getNextRangeOffset(); |
---|
1716 | readBuffer.length = HTTP_REQBUF_SZ; |
---|
1717 | readBuffer.data = reqbuf; |
---|
1718 | /* we may note we have reached the end of the wanted ranges */ |
---|
1719 | clientStreamRead(getTail(), http, readBuffer); |
---|
1720 | } |
---|
1721 | |
---|
1722 | /** Adapt stream status to account for Range cases |
---|
1723 | * |
---|
1724 | */ |
---|
1725 | clientStream_status_t |
---|
1726 | ClientSocketContext::socketState() |
---|
1727 | { |
---|
1728 | switch (clientStreamStatus(getTail(), http)) { |
---|
1729 | |
---|
1730 | case STREAM_NONE: |
---|
1731 | /* check for range support ending */ |
---|
1732 | |
---|
1733 | if (http->request->range) { |
---|
1734 | /* check: reply was parsed and range iterator was initialized */ |
---|
1735 | assert(http->range_iter.valid); |
---|
1736 | /* filter out data according to range specs */ |
---|
1737 | |
---|
1738 | if (!canPackMoreRanges()) { |
---|
1739 | debugs(33, 5, HERE << "Range request at end of returnable " << |
---|
1740 | "range sequence on " << clientConnection); |
---|
1741 | // we got everything we wanted from the store |
---|
1742 | return STREAM_COMPLETE; |
---|
1743 | } |
---|
1744 | } else if (reply && reply->content_range) { |
---|
1745 | /* reply has content-range, but Squid is not managing ranges */ |
---|
1746 | const int64_t &bytesSent = http->out.offset; |
---|
1747 | const int64_t &bytesExpected = reply->content_range->spec.length; |
---|
1748 | |
---|
1749 | debugs(33, 7, HERE << "body bytes sent vs. expected: " << |
---|
1750 | bytesSent << " ? " << bytesExpected << " (+" << |
---|
1751 | reply->content_range->spec.offset << ")"); |
---|
1752 | |
---|
1753 | // did we get at least what we expected, based on range specs? |
---|
1754 | |
---|
1755 | if (bytesSent == bytesExpected) // got everything |
---|
1756 | return STREAM_COMPLETE; |
---|
1757 | |
---|
1758 | if (bytesSent > bytesExpected) // Error: Sent more than expected |
---|
1759 | return STREAM_UNPLANNED_COMPLETE; |
---|
1760 | } |
---|
1761 | |
---|
1762 | return STREAM_NONE; |
---|
1763 | |
---|
1764 | case STREAM_COMPLETE: |
---|
1765 | return STREAM_COMPLETE; |
---|
1766 | |
---|
1767 | case STREAM_UNPLANNED_COMPLETE: |
---|
1768 | return STREAM_UNPLANNED_COMPLETE; |
---|
1769 | |
---|
1770 | case STREAM_FAILED: |
---|
1771 | return STREAM_FAILED; |
---|
1772 | } |
---|
1773 | |
---|
1774 | fatal ("unreachable code\n"); |
---|
1775 | return STREAM_NONE; |
---|
1776 | } |
---|
1777 | |
---|
1778 | /** |
---|
1779 | * A write has just completed to the client, or we have just realised there is |
---|
1780 | * no more data to send. |
---|
1781 | */ |
---|
1782 | void |
---|
1783 | clientWriteComplete(const Comm::ConnectionPointer &conn, char *bufnotused, size_t size, Comm::Flag errflag, int xerrno, void *data) |
---|
1784 | { |
---|
1785 | ClientSocketContext *context = (ClientSocketContext *)data; |
---|
1786 | context->writeComplete(conn, bufnotused, size, errflag); |
---|
1787 | } |
---|
1788 | |
---|
1789 | /// remembers the abnormal connection termination for logging purposes |
---|
1790 | void |
---|
1791 | ClientSocketContext::noteIoError(const int xerrno) |
---|
1792 | { |
---|
1793 | if (http) { |
---|
1794 | if (xerrno == ETIMEDOUT) |
---|
1795 | http->al->http.timedout = true; |
---|
1796 | else // even if xerrno is zero (which means read abort/eof) |
---|
1797 | http->al->http.aborted = true; |
---|
1798 | } |
---|
1799 | } |
---|
1800 | |
---|
1801 | void |
---|
1802 | ClientSocketContext::doClose() |
---|
1803 | { |
---|
1804 | clientConnection->close(); |
---|
1805 | } |
---|
1806 | |
---|
1807 | /// called when we encounter a response-related error |
---|
1808 | void |
---|
1809 | ClientSocketContext::initiateClose(const char *reason) |
---|
1810 | { |
---|
1811 | http->getConn()->stopSending(reason); // closes ASAP |
---|
1812 | } |
---|
1813 | |
---|
1814 | void |
---|
1815 | ConnStateData::stopSending(const char *error) |
---|
1816 | { |
---|
1817 | debugs(33, 4, HERE << "sending error (" << clientConnection << "): " << error << |
---|
1818 | "; old receiving error: " << |
---|
1819 | (stoppedReceiving() ? stoppedReceiving_ : "none")); |
---|
1820 | |
---|
1821 | if (const char *oldError = stoppedSending()) { |
---|
1822 | debugs(33, 3, HERE << "already stopped sending: " << oldError); |
---|
1823 | return; // nothing has changed as far as this connection is concerned |
---|
1824 | } |
---|
1825 | stoppedSending_ = error; |
---|
1826 | |
---|
1827 | if (!stoppedReceiving()) { |
---|
1828 | if (const int64_t expecting = mayNeedToReadMoreBody()) { |
---|
1829 | debugs(33, 5, HERE << "must still read " << expecting << |
---|
1830 | " request body bytes with " << in.buf.length() << " unused"); |
---|
1831 | return; // wait for the request receiver to finish reading |
---|
1832 | } |
---|
1833 | } |
---|
1834 | |
---|
1835 | clientConnection->close(); |
---|
1836 | } |
---|
1837 | |
---|
1838 | void |
---|
1839 | ClientSocketContext::writeComplete(const Comm::ConnectionPointer &conn, char *bufnotused, size_t size, Comm::Flag errflag) |
---|
1840 | { |
---|
1841 | const StoreEntry *entry = http->storeEntry(); |
---|
1842 | http->out.size += size; |
---|
1843 | debugs(33, 5, HERE << conn << ", sz " << size << |
---|
1844 | ", err " << errflag << ", off " << http->out.size << ", len " << |
---|
1845 | (entry ? entry->objectLen() : 0)); |
---|
1846 | clientUpdateSocketStats(http->logType, size); |
---|
1847 | |
---|
1848 | /* Bail out quickly on Comm::ERR_CLOSING - close handlers will tidy up */ |
---|
1849 | |
---|
1850 | if (errflag == Comm::ERR_CLOSING || !Comm::IsConnOpen(conn)) |
---|
1851 | return; |
---|
1852 | |
---|
1853 | if (errflag || clientHttpRequestStatus(conn->fd, http)) { |
---|
1854 | initiateClose("failure or true request status"); |
---|
1855 | /* Do we leak here ? */ |
---|
1856 | return; |
---|
1857 | } |
---|
1858 | |
---|
1859 | switch (socketState()) { |
---|
1860 | |
---|
1861 | case STREAM_NONE: |
---|
1862 | pullData(); |
---|
1863 | break; |
---|
1864 | |
---|
1865 | case STREAM_COMPLETE: |
---|
1866 | debugs(33, 5, conn << " Stream complete, keepalive is " << http->request->flags.proxyKeepalive); |
---|
1867 | if (http->request->flags.proxyKeepalive) |
---|
1868 | keepaliveNextRequest(); |
---|
1869 | else |
---|
1870 | initiateClose("STREAM_COMPLETE NOKEEPALIVE"); |
---|
1871 | return; |
---|
1872 | |
---|
1873 | case STREAM_UNPLANNED_COMPLETE: |
---|
1874 | initiateClose("STREAM_UNPLANNED_COMPLETE"); |
---|
1875 | return; |
---|
1876 | |
---|
1877 | case STREAM_FAILED: |
---|
1878 | initiateClose("STREAM_FAILED"); |
---|
1879 | return; |
---|
1880 | |
---|
1881 | default: |
---|
1882 | fatal("Hit unreachable code in clientWriteComplete\n"); |
---|
1883 | } |
---|
1884 | } |
---|
1885 | |
---|
1886 | ClientSocketContext * |
---|
1887 | ConnStateData::abortRequestParsing(const char *const uri) |
---|
1888 | { |
---|
1889 | ClientHttpRequest *http = new ClientHttpRequest(this); |
---|
1890 | http->req_sz = in.buf.length(); |
---|
1891 | http->uri = xstrdup(uri); |
---|
1892 | setLogUri (http, uri); |
---|
1893 | ClientSocketContext *context = new ClientSocketContext(clientConnection, http); |
---|
1894 | StoreIOBuffer tempBuffer; |
---|
1895 | tempBuffer.data = context->reqbuf; |
---|
1896 | tempBuffer.length = HTTP_REQBUF_SZ; |
---|
1897 | clientStreamInit(&http->client_stream, clientGetMoreData, clientReplyDetach, |
---|
1898 | clientReplyStatus, new clientReplyContext(http), clientSocketRecipient, |
---|
1899 | clientSocketDetach, context, tempBuffer); |
---|
1900 | return context; |
---|
1901 | } |
---|
1902 | |
---|
1903 | void |
---|
1904 | ConnStateData::startShutdown() |
---|
1905 | { |
---|
1906 | // RegisteredRunner API callback - Squid has been shut down |
---|
1907 | |
---|
1908 | // if connection is idle terminate it now, |
---|
1909 | // otherwise wait for grace period to end |
---|
1910 | if (getConcurrentRequestCount() == 0) |
---|
1911 | endingShutdown(); |
---|
1912 | } |
---|
1913 | |
---|
1914 | void |
---|
1915 | ConnStateData::endingShutdown() |
---|
1916 | { |
---|
1917 | // RegisteredRunner API callback - Squid shutdown grace period is over |
---|
1918 | |
---|
1919 | // force the client connection to close immediately |
---|
1920 | // swanSong() in the close handler will cleanup. |
---|
1921 | if (Comm::IsConnOpen(clientConnection)) |
---|
1922 | clientConnection->close(); |
---|
1923 | |
---|
1924 | // deregister now to ensure finalShutdown() does not kill us prematurely. |
---|
1925 | // fd_table purge will cleanup if close handler was not fast enough. |
---|
1926 | DeregisterRunner(this); |
---|
1927 | } |
---|
1928 | |
---|
1929 | char * |
---|
1930 | skipLeadingSpace(char *aString) |
---|
1931 | { |
---|
1932 | char *result = aString; |
---|
1933 | |
---|
1934 | while (xisspace(*aString)) |
---|
1935 | ++aString; |
---|
1936 | |
---|
1937 | return result; |
---|
1938 | } |
---|
1939 | |
---|
1940 | /** |
---|
1941 | * 'end' defaults to NULL for backwards compatibility |
---|
1942 | * remove default value if we ever get rid of NULL-terminated |
---|
1943 | * request buffers. |
---|
1944 | */ |
---|
1945 | const char * |
---|
1946 | findTrailingHTTPVersion(const char *uriAndHTTPVersion, const char *end) |
---|
1947 | { |
---|
1948 | if (NULL == end) { |
---|
1949 | end = uriAndHTTPVersion + strcspn(uriAndHTTPVersion, "\r\n"); |
---|
1950 | assert(end); |
---|
1951 | } |
---|
1952 | |
---|
1953 | for (; end > uriAndHTTPVersion; --end) { |
---|
1954 | if (*end == '\n' || *end == '\r') |
---|
1955 | continue; |
---|
1956 | |
---|
1957 | if (xisspace(*end)) { |
---|
1958 | if (strncasecmp(end + 1, "HTTP/", 5) == 0) |
---|
1959 | return end + 1; |
---|
1960 | else |
---|
1961 | break; |
---|
1962 | } |
---|
1963 | } |
---|
1964 | |
---|
1965 | return NULL; |
---|
1966 | } |
---|
1967 | |
---|
1968 | void |
---|
1969 | setLogUri(ClientHttpRequest * http, char const *uri, bool cleanUrl) |
---|
1970 | { |
---|
1971 | safe_free(http->log_uri); |
---|
1972 | |
---|
1973 | if (!cleanUrl) |
---|
1974 | // The uri is already clean just dump it. |
---|
1975 | http->log_uri = xstrndup(uri, MAX_URL); |
---|
1976 | else { |
---|
1977 | int flags = 0; |
---|
1978 | switch (Config.uri_whitespace) { |
---|
1979 | case URI_WHITESPACE_ALLOW: |
---|
1980 | flags |= RFC1738_ESCAPE_NOSPACE; |
---|
1981 | |
---|
1982 | case URI_WHITESPACE_ENCODE: |
---|
1983 | flags |= RFC1738_ESCAPE_UNESCAPED; |
---|
1984 | http->log_uri = xstrndup(rfc1738_do_escape(uri, flags), MAX_URL); |
---|
1985 | break; |
---|
1986 | |
---|
1987 | case URI_WHITESPACE_CHOP: { |
---|
1988 | flags |= RFC1738_ESCAPE_NOSPACE; |
---|
1989 | flags |= RFC1738_ESCAPE_UNESCAPED; |
---|
1990 | http->log_uri = xstrndup(rfc1738_do_escape(uri, flags), MAX_URL); |
---|
1991 | int pos = strcspn(http->log_uri, w_space); |
---|
1992 | http->log_uri[pos] = '\0'; |
---|
1993 | } |
---|
1994 | break; |
---|
1995 | |
---|
1996 | case URI_WHITESPACE_DENY: |
---|
1997 | case URI_WHITESPACE_STRIP: |
---|
1998 | default: { |
---|
1999 | const char *t; |
---|
2000 | char *tmp_uri = static_cast<char*>(xmalloc(strlen(uri) + 1)); |
---|
2001 | char *q = tmp_uri; |
---|
2002 | t = uri; |
---|
2003 | while (*t) { |
---|
2004 | if (!xisspace(*t)) { |
---|
2005 | *q = *t; |
---|
2006 | ++q; |
---|
2007 | } |
---|
2008 | ++t; |
---|
2009 | } |
---|
2010 | *q = '\0'; |
---|
2011 | http->log_uri = xstrndup(rfc1738_escape_unescaped(tmp_uri), MAX_URL); |
---|
2012 | xfree(tmp_uri); |
---|
2013 | } |
---|
2014 | break; |
---|
2015 | } |
---|
2016 | } |
---|
2017 | } |
---|
2018 | |
---|
2019 | static void |
---|
2020 | prepareAcceleratedURL(ConnStateData * conn, ClientHttpRequest *http, char *url, const char *req_hdr) |
---|
2021 | { |
---|
2022 | int vhost = conn->port->vhost; |
---|
2023 | int vport = conn->port->vport; |
---|
2024 | char *host; |
---|
2025 | char ipbuf[MAX_IPSTRLEN]; |
---|
2026 | |
---|
2027 | http->flags.accel = true; |
---|
2028 | |
---|
2029 | /* BUG: Squid cannot deal with '*' URLs (RFC2616 5.1.2) */ |
---|
2030 | |
---|
2031 | if (strncasecmp(url, "cache_object://", 15) == 0) |
---|
2032 | return; /* already in good shape */ |
---|
2033 | |
---|
2034 | if (*url != '/') { |
---|
2035 | if (conn->port->vhost) |
---|
2036 | return; /* already in good shape */ |
---|
2037 | |
---|
2038 | /* else we need to ignore the host name */ |
---|
2039 | url = strstr(url, "//"); |
---|
2040 | |
---|
2041 | #if SHOULD_REJECT_UNKNOWN_URLS |
---|
2042 | |
---|
2043 | if (!url) { |
---|
2044 | hp->request_parse_status = Http::scBadRequest; |
---|
2045 | return conn->abortRequestParsing("error:invalid-request"); |
---|
2046 | } |
---|
2047 | #endif |
---|
2048 | |
---|
2049 | if (url) |
---|
2050 | url = strchr(url + 2, '/'); |
---|
2051 | |
---|
2052 | if (!url) |
---|
2053 | url = (char *) "/"; |
---|
2054 | } |
---|
2055 | |
---|
2056 | if (vport < 0) |
---|
2057 | vport = http->getConn()->clientConnection->local.port(); |
---|
2058 | |
---|
2059 | const bool switchedToHttps = conn->switchedToHttps(); |
---|
2060 | const bool tryHostHeader = vhost || switchedToHttps; |
---|
2061 | if (tryHostHeader && (host = mime_get_header(req_hdr, "Host")) != NULL) { |
---|
2062 | debugs(33, 5, "ACCEL VHOST REWRITE: vhost=" << host << " + vport=" << vport); |
---|
2063 | char thost[256]; |
---|
2064 | if (vport > 0) { |
---|
2065 | thost[0] = '\0'; |
---|
2066 | char *t = NULL; |
---|
2067 | if (host[strlen(host)] != ']' && (t = strrchr(host,':')) != NULL) { |
---|
2068 | strncpy(thost, host, (t-host)); |
---|
2069 | snprintf(thost+(t-host), sizeof(thost)-(t-host), ":%d", vport); |
---|
2070 | host = thost; |
---|
2071 | } else if (!t) { |
---|
2072 | snprintf(thost, sizeof(thost), "%s:%d",host, vport); |
---|
2073 | host = thost; |
---|
2074 | } |
---|
2075 | } // else nothing to alter port-wise. |
---|
2076 | int url_sz = strlen(url) + 32 + Config.appendDomainLen + |
---|
2077 | strlen(host); |
---|
2078 | http->uri = (char *)xcalloc(url_sz, 1); |
---|
2079 | const char *protocol = switchedToHttps ? |
---|
2080 | "https" : AnyP::UriScheme(conn->port->transport.protocol).c_str(); |
---|
2081 | snprintf(http->uri, url_sz, "%s://%s%s", protocol, host, url); |
---|
2082 | debugs(33, 5, "ACCEL VHOST REWRITE: '" << http->uri << "'"); |
---|
2083 | } else if (conn->port->defaultsite /* && !vhost */) { |
---|
2084 | debugs(33, 5, "ACCEL DEFAULTSITE REWRITE: defaultsite=" << conn->port->defaultsite << " + vport=" << vport); |
---|
2085 | int url_sz = strlen(url) + 32 + Config.appendDomainLen + |
---|
2086 | strlen(conn->port->defaultsite); |
---|
2087 | http->uri = (char *)xcalloc(url_sz, 1); |
---|
2088 | char vportStr[32]; |
---|
2089 | vportStr[0] = '\0'; |
---|
2090 | if (vport > 0) { |
---|
2091 | snprintf(vportStr, sizeof(vportStr),":%d",vport); |
---|
2092 | } |
---|
2093 | snprintf(http->uri, url_sz, "%s://%s%s%s", |
---|
2094 | AnyP::UriScheme(conn->port->transport.protocol).c_str(), conn->port->defaultsite, vportStr, url); |
---|
2095 | debugs(33, 5, "ACCEL DEFAULTSITE REWRITE: '" << http->uri <<"'"); |
---|
2096 | } else if (vport > 0 /* && (!vhost || no Host:) */) { |
---|
2097 | debugs(33, 5, "ACCEL VPORT REWRITE: *_port IP + vport=" << vport); |
---|
2098 | /* Put the local socket IP address as the hostname, with whatever vport we found */ |
---|
2099 | int url_sz = strlen(url) + 32 + Config.appendDomainLen; |
---|
2100 | http->uri = (char *)xcalloc(url_sz, 1); |
---|
2101 | http->getConn()->clientConnection->local.toHostStr(ipbuf,MAX_IPSTRLEN); |
---|
2102 | snprintf(http->uri, url_sz, "%s://%s:%d%s", |
---|
2103 | AnyP::UriScheme(conn->port->transport.protocol).c_str(), |
---|
2104 | ipbuf, vport, url); |
---|
2105 | debugs(33, 5, "ACCEL VPORT REWRITE: '" << http->uri << "'"); |
---|
2106 | } |
---|
2107 | } |
---|
2108 | |
---|
2109 | static void |
---|
2110 | prepareTransparentURL(ConnStateData * conn, ClientHttpRequest *http, char *url, const char *req_hdr) |
---|
2111 | { |
---|
2112 | char *host; |
---|
2113 | char ipbuf[MAX_IPSTRLEN]; |
---|
2114 | |
---|
2115 | if (*url != '/') |
---|
2116 | return; /* already in good shape */ |
---|
2117 | |
---|
2118 | /* BUG: Squid cannot deal with '*' URLs (RFC2616 5.1.2) */ |
---|
2119 | |
---|
2120 | if ((host = mime_get_header(req_hdr, "Host")) != NULL) { |
---|
2121 | int url_sz = strlen(url) + 32 + Config.appendDomainLen + |
---|
2122 | strlen(host); |
---|
2123 | http->uri = (char *)xcalloc(url_sz, 1); |
---|
2124 | snprintf(http->uri, url_sz, "%s://%s%s", AnyP::UriScheme(conn->port->transport.protocol).c_str(), host, url); |
---|
2125 | debugs(33, 5, "TRANSPARENT HOST REWRITE: '" << http->uri <<"'"); |
---|
2126 | } else { |
---|
2127 | /* Put the local socket IP address as the hostname. */ |
---|
2128 | int url_sz = strlen(url) + 32 + Config.appendDomainLen; |
---|
2129 | http->uri = (char *)xcalloc(url_sz, 1); |
---|
2130 | http->getConn()->clientConnection->local.toHostStr(ipbuf,MAX_IPSTRLEN); |
---|
2131 | snprintf(http->uri, url_sz, "%s://%s:%d%s", |
---|
2132 | AnyP::UriScheme(http->getConn()->port->transport.protocol).c_str(), |
---|
2133 | ipbuf, http->getConn()->clientConnection->local.port(), url); |
---|
2134 | debugs(33, 5, "TRANSPARENT REWRITE: '" << http->uri << "'"); |
---|
2135 | } |
---|
2136 | } |
---|
2137 | |
---|
2138 | /** Parse an HTTP request |
---|
2139 | * |
---|
2140 | * \note Sets result->flags.parsed_ok to 0 if failed to parse the request, |
---|
2141 | * to 1 if the request was correctly parsed. |
---|
2142 | * \param[in] csd a ConnStateData. The caller must make sure it is not null |
---|
2143 | * \param[in] hp an HttpParser |
---|
2144 | * \param[out] mehtod_p will be set as a side-effect of the parsing. |
---|
2145 | * Pointed-to value will be set to Http::METHOD_NONE in case of |
---|
2146 | * parsing failure |
---|
2147 | * \param[out] http_ver will be set as a side-effect of the parsing |
---|
2148 | * \return NULL on incomplete requests, |
---|
2149 | * a ClientSocketContext structure on success or failure. |
---|
2150 | */ |
---|
2151 | ClientSocketContext * |
---|
2152 | parseHttpRequest(ConnStateData *csd, HttpParser *hp, HttpRequestMethod * method_p, Http::ProtocolVersion *http_ver) |
---|
2153 | { |
---|
2154 | char *req_hdr = NULL; |
---|
2155 | char *end; |
---|
2156 | size_t req_sz; |
---|
2157 | ClientHttpRequest *http; |
---|
2158 | ClientSocketContext *result; |
---|
2159 | StoreIOBuffer tempBuffer; |
---|
2160 | int r; |
---|
2161 | |
---|
2162 | /* pre-set these values to make aborting simpler */ |
---|
2163 | *method_p = Http::METHOD_NONE; |
---|
2164 | |
---|
2165 | /* NP: don't be tempted to move this down or remove again. |
---|
2166 | * It's the only DDoS protection old-String has against long URL */ |
---|
2167 | if ( hp->bufsiz <= 0) { |
---|
2168 | debugs(33, 5, "Incomplete request, waiting for end of request line"); |
---|
2169 | return NULL; |
---|
2170 | } else if ( (size_t)hp->bufsiz >= Config.maxRequestHeaderSize && headersEnd(hp->buf, Config.maxRequestHeaderSize) == 0) { |
---|
2171 | debugs(33, 5, "parseHttpRequest: Too large request"); |
---|
2172 | hp->request_parse_status = Http::scHeaderTooLarge; |
---|
2173 | return csd->abortRequestParsing("error:request-too-large"); |
---|
2174 | } |
---|
2175 | |
---|
2176 | /* Attempt to parse the first line; this'll define the method, url, version and header begin */ |
---|
2177 | r = HttpParserParseReqLine(hp); |
---|
2178 | |
---|
2179 | if (r == 0) { |
---|
2180 | debugs(33, 5, "Incomplete request, waiting for end of request line"); |
---|
2181 | return NULL; |
---|
2182 | } |
---|
2183 | |
---|
2184 | if (r == -1) { |
---|
2185 | return csd->abortRequestParsing("error:invalid-request"); |
---|
2186 | } |
---|
2187 | |
---|
2188 | /* Request line is valid here .. */ |
---|
2189 | *http_ver = Http::ProtocolVersion(hp->req.v_maj, hp->req.v_min); |
---|
2190 | |
---|
2191 | /* This call scans the entire request, not just the headers */ |
---|
2192 | if (hp->req.v_maj > 0) { |
---|
2193 | if ((req_sz = headersEnd(hp->buf, hp->bufsiz)) == 0) { |
---|
2194 | debugs(33, 5, "Incomplete request, waiting for end of headers"); |
---|
2195 | return NULL; |
---|
2196 | } |
---|
2197 | } else { |
---|
2198 | debugs(33, 3, "parseHttpRequest: Missing HTTP identifier"); |
---|
2199 | req_sz = HttpParserReqSz(hp); |
---|
2200 | } |
---|
2201 | |
---|
2202 | /* We know the whole request is in hp->buf now */ |
---|
2203 | |
---|
2204 | assert(req_sz <= (size_t) hp->bufsiz); |
---|
2205 | |
---|
2206 | /* Will the following be true with HTTP/0.9 requests? probably not .. */ |
---|
2207 | /* So the rest of the code will need to deal with '0'-byte headers (ie, none, so don't try parsing em) */ |
---|
2208 | assert(req_sz > 0); |
---|
2209 | |
---|
2210 | hp->hdr_end = req_sz - 1; |
---|
2211 | |
---|
2212 | hp->hdr_start = hp->req.end + 1; |
---|
2213 | |
---|
2214 | /* Enforce max_request_size */ |
---|
2215 | if (req_sz >= Config.maxRequestHeaderSize) { |
---|
2216 | debugs(33, 5, "parseHttpRequest: Too large request"); |
---|
2217 | hp->request_parse_status = Http::scHeaderTooLarge; |
---|
2218 | return csd->abortRequestParsing("error:request-too-large"); |
---|
2219 | } |
---|
2220 | |
---|
2221 | /* Set method_p */ |
---|
2222 | *method_p = HttpRequestMethod(&hp->buf[hp->req.m_start], &hp->buf[hp->req.m_end]+1); |
---|
2223 | |
---|
2224 | /* deny CONNECT via accelerated ports */ |
---|
2225 | if (*method_p == Http::METHOD_CONNECT && csd->port != NULL && csd->port->flags.accelSurrogate) { |
---|
2226 | debugs(33, DBG_IMPORTANT, "WARNING: CONNECT method received on " << csd->port->transport.protocol << " Accelerator port " << csd->port->s.port()); |
---|
2227 | /* XXX need a way to say "this many character length string" */ |
---|
2228 | debugs(33, DBG_IMPORTANT, "WARNING: for request: " << hp->buf); |
---|
2229 | hp->request_parse_status = Http::scMethodNotAllowed; |
---|
2230 | return csd->abortRequestParsing("error:method-not-allowed"); |
---|
2231 | } |
---|
2232 | |
---|
2233 | /* RFC 7540 section 11.6 registers the method PRI as HTTP/2 specific |
---|
2234 | * Deny "PRI" method if used in HTTP/1.x or 0.9 versions. |
---|
2235 | * If seen it signals a broken client or proxy has corrupted the traffic. |
---|
2236 | */ |
---|
2237 | if (*method_p == Http::METHOD_PRI && *http_ver < Http::ProtocolVersion(2,0)) { |
---|
2238 | debugs(33, DBG_IMPORTANT, "WARNING: PRI method received on " << csd->port->transport.protocol << "port " << csd->port->s.port()); |
---|
2239 | debugs(33, DBG_IMPORTANT, "WARNING: for request: " << hp->buf); |
---|
2240 | hp->request_parse_status = Http::scMethodNotAllowed; |
---|
2241 | return csd->abortRequestParsing("error:method-not-allowed"); |
---|
2242 | } |
---|
2243 | |
---|
2244 | if (*method_p == Http::METHOD_NONE) { |
---|
2245 | /* XXX need a way to say "this many character length string" */ |
---|
2246 | debugs(33, DBG_IMPORTANT, "clientParseRequestMethod: Unsupported method in request '" << hp->buf << "'"); |
---|
2247 | hp->request_parse_status = Http::scMethodNotAllowed; |
---|
2248 | return csd->abortRequestParsing("error:unsupported-request-method"); |
---|
2249 | } |
---|
2250 | |
---|
2251 | /* |
---|
2252 | * Process headers after request line |
---|
2253 | * TODO: Use httpRequestParse here. |
---|
2254 | */ |
---|
2255 | /* XXX this code should be modified to take a const char * later! */ |
---|
2256 | req_hdr = (char *) hp->buf + hp->req.end + 1; |
---|
2257 | |
---|
2258 | debugs(33, 3, "parseHttpRequest: req_hdr = {" << req_hdr << "}"); |
---|
2259 | |
---|
2260 | end = (char *) hp->buf + hp->hdr_end; |
---|
2261 | |
---|
2262 | debugs(33, 3, "parseHttpRequest: end = {" << end << "}"); |
---|
2263 | |
---|
2264 | debugs(33, 3, "parseHttpRequest: prefix_sz = " << |
---|
2265 | (int) HttpParserRequestLen(hp) << ", req_line_sz = " << |
---|
2266 | HttpParserReqSz(hp)); |
---|
2267 | |
---|
2268 | /* Ok, all headers are received */ |
---|
2269 | http = new ClientHttpRequest(csd); |
---|
2270 | |
---|
2271 | http->req_sz = HttpParserRequestLen(hp); |
---|
2272 | result = new ClientSocketContext(csd->clientConnection, http); |
---|
2273 | tempBuffer.data = result->reqbuf; |
---|
2274 | tempBuffer.length = HTTP_REQBUF_SZ; |
---|
2275 | |
---|
2276 | ClientStreamData newServer = new clientReplyContext(http); |
---|
2277 | ClientStreamData newClient = result; |
---|
2278 | clientStreamInit(&http->client_stream, clientGetMoreData, clientReplyDetach, |
---|
2279 | clientReplyStatus, newServer, clientSocketRecipient, |
---|
2280 | clientSocketDetach, newClient, tempBuffer); |
---|
2281 | |
---|
2282 | debugs(33, 5, "parseHttpRequest: Request Header is\n" <<(hp->buf) + hp->hdr_start); |
---|
2283 | |
---|
2284 | /* set url */ |
---|
2285 | /* |
---|
2286 | * XXX this should eventually not use a malloc'ed buffer; the transformation code |
---|
2287 | * below needs to be modified to not expect a mutable nul-terminated string. |
---|
2288 | */ |
---|
2289 | char *url = (char *)xmalloc(hp->req.u_end - hp->req.u_start + 16); |
---|
2290 | |
---|
2291 | memcpy(url, hp->buf + hp->req.u_start, hp->req.u_end - hp->req.u_start + 1); |
---|
2292 | |
---|
2293 | url[hp->req.u_end - hp->req.u_start + 1] = '\0'; |
---|
2294 | |
---|
2295 | #if THIS_VIOLATES_HTTP_SPECS_ON_URL_TRANSFORMATION |
---|
2296 | |
---|
2297 | if ((t = strchr(url, '#'))) /* remove HTML anchors */ |
---|
2298 | *t = '\0'; |
---|
2299 | |
---|
2300 | #endif |
---|
2301 | |
---|
2302 | debugs(33,5, "Prepare absolute URL from " << |
---|
2303 | (csd->transparent()?"intercept":(csd->port->flags.accelSurrogate ? "accel":""))); |
---|
2304 | /* Rewrite the URL in transparent or accelerator mode */ |
---|
2305 | /* NP: there are several cases to traverse here: |
---|
2306 | * - standard mode (forward proxy) |
---|
2307 | * - transparent mode (TPROXY) |
---|
2308 | * - transparent mode with failures |
---|
2309 | * - intercept mode (NAT) |
---|
2310 | * - intercept mode with failures |
---|
2311 | * - accelerator mode (reverse proxy) |
---|
2312 | * - internal URL |
---|
2313 | * - mixed combos of the above with internal URL |
---|
2314 | * - remote interception with PROXY protocol |
---|
2315 | * - remote reverse-proxy with PROXY protocol |
---|
2316 | */ |
---|
2317 | if (csd->transparent()) { |
---|
2318 | /* intercept or transparent mode, properly working with no failures */ |
---|
2319 | prepareTransparentURL(csd, http, url, req_hdr); |
---|
2320 | |
---|
2321 | } else if (internalCheck(url)) { |
---|
2322 | /* internal URL mode */ |
---|
2323 | /* prepend our name & port */ |
---|
2324 | http->uri = xstrdup(internalLocalUri(NULL, url)); |
---|
2325 | // We just re-wrote the URL. Must replace the Host: header. |
---|
2326 | // But have not parsed there yet!! flag for local-only handling. |
---|
2327 | http->flags.internal = true; |
---|
2328 | |
---|
2329 | } else if (csd->port->flags.accelSurrogate || csd->switchedToHttps()) { |
---|
2330 | /* accelerator mode */ |
---|
2331 | prepareAcceleratedURL(csd, http, url, req_hdr); |
---|
2332 | } |
---|
2333 | |
---|
2334 | if (!http->uri) { |
---|
2335 | /* No special rewrites have been applied above, use the |
---|
2336 | * requested url. may be rewritten later, so make extra room */ |
---|
2337 | int url_sz = strlen(url) + Config.appendDomainLen + 5; |
---|
2338 | http->uri = (char *)xcalloc(url_sz, 1); |
---|
2339 | strcpy(http->uri, url); |
---|
2340 | } |
---|
2341 | |
---|
2342 | debugs(33, 5, "parseHttpRequest: Complete request received"); |
---|
2343 | |
---|
2344 | // XXX: crop this dump at the end of headers. No need for extras |
---|
2345 | debugs(11, 2, "HTTP Client " << csd->clientConnection); |
---|
2346 | debugs(11, 2, "HTTP Client REQUEST:\n---------\n" << (hp->buf) + hp->req.m_start << "\n----------"); |
---|
2347 | |
---|
2348 | result->flags.parsed_ok = 1; |
---|
2349 | xfree(url); |
---|
2350 | return result; |
---|
2351 | } |
---|
2352 | |
---|
2353 | bool |
---|
2354 | ConnStateData::In::maybeMakeSpaceAvailable() |
---|
2355 | { |
---|
2356 | if (buf.spaceSize() < 2) { |
---|
2357 | const SBuf::size_type haveCapacity = buf.length() + buf.spaceSize(); |
---|
2358 | if (haveCapacity >= Config.maxRequestBufferSize) { |
---|
2359 | debugs(33, 4, "request buffer full: client_request_buffer_max_size=" << Config.maxRequestBufferSize); |
---|
2360 | return false; |
---|
2361 | } |
---|
2362 | if (haveCapacity == 0) { |
---|
2363 | // haveCapacity is based on the SBuf visible window of the MemBlob buffer, which may fill up. |
---|
2364 | // at which point bump the buffer back to default. This allocates a new MemBlob with any un-parsed bytes. |
---|
2365 | buf.reserveCapacity(CLIENT_REQ_BUF_SZ); |
---|
2366 | } else { |
---|
2367 | const SBuf::size_type wantCapacity = min(static_cast<SBuf::size_type>(Config.maxRequestBufferSize), haveCapacity*2); |
---|
2368 | buf.reserveCapacity(wantCapacity); |
---|
2369 | } |
---|
2370 | debugs(33, 2, "growing request buffer: available=" << buf.spaceSize() << " used=" << buf.length()); |
---|
2371 | } |
---|
2372 | return (buf.spaceSize() >= 2); |
---|
2373 | } |
---|
2374 | |
---|
2375 | void |
---|
2376 | ConnStateData::addContextToQueue(ClientSocketContext * context) |
---|
2377 | { |
---|
2378 | ClientSocketContext::Pointer *S; |
---|
2379 | |
---|
2380 | for (S = (ClientSocketContext::Pointer *) & currentobject; S->getRaw(); |
---|
2381 | S = &(*S)->next); |
---|
2382 | *S = context; |
---|
2383 | |
---|
2384 | ++nrequests; |
---|
2385 | } |
---|
2386 | |
---|
2387 | int |
---|
2388 | ConnStateData::getConcurrentRequestCount() const |
---|
2389 | { |
---|
2390 | int result = 0; |
---|
2391 | ClientSocketContext::Pointer *T; |
---|
2392 | |
---|
2393 | for (T = (ClientSocketContext::Pointer *) ¤tobject; |
---|
2394 | T->getRaw(); T = &(*T)->next, ++result); |
---|
2395 | return result; |
---|
2396 | } |
---|
2397 | |
---|
2398 | int |
---|
2399 | ConnStateData::connFinishedWithConn(int size) |
---|
2400 | { |
---|
2401 | if (size == 0) { |
---|
2402 | if (getConcurrentRequestCount() == 0 && in.buf.isEmpty()) { |
---|
2403 | /* no current or pending requests */ |
---|
2404 | debugs(33, 4, HERE << clientConnection << " closed"); |
---|
2405 | return 1; |
---|
2406 | } else if (!Config.onoff.half_closed_clients) { |
---|
2407 | /* admin doesn't want to support half-closed client sockets */ |
---|
2408 | debugs(33, 3, HERE << clientConnection << " aborted (half_closed_clients disabled)"); |
---|
2409 | notifyAllContexts(0); // no specific error implies abort |
---|
2410 | return 1; |
---|
2411 | } |
---|
2412 | } |
---|
2413 | |
---|
2414 | return 0; |
---|
2415 | } |
---|
2416 | |
---|
2417 | void |
---|
2418 | ConnStateData::consumeInput(const size_t byteCount) |
---|
2419 | { |
---|
2420 | assert(byteCount > 0 && byteCount <= in.buf.length()); |
---|
2421 | in.buf.consume(byteCount); |
---|
2422 | debugs(33, 5, "in.buf has " << in.buf.length() << " unused bytes"); |
---|
2423 | } |
---|
2424 | |
---|
2425 | // TODO: Remove when renaming ConnStateData |
---|
2426 | void |
---|
2427 | connNoteUseOfBuffer(ConnStateData* conn, size_t byteCount) |
---|
2428 | { |
---|
2429 | conn->consumeInput(byteCount); |
---|
2430 | } |
---|
2431 | |
---|
2432 | void |
---|
2433 | ConnStateData::clientAfterReadingRequests() |
---|
2434 | { |
---|
2435 | // Were we expecting to read more request body from half-closed connection? |
---|
2436 | if (mayNeedToReadMoreBody() && commIsHalfClosed(clientConnection->fd)) { |
---|
2437 | debugs(33, 3, HERE << "truncated body: closing half-closed " << clientConnection); |
---|
2438 | clientConnection->close(); |
---|
2439 | return; |
---|
2440 | } |
---|
2441 | |
---|
2442 | if (flags.readMore) |
---|
2443 | readSomeData(); |
---|
2444 | } |
---|
2445 | |
---|
2446 | void |
---|
2447 | ConnStateData::quitAfterError(HttpRequest *request) |
---|
2448 | { |
---|
2449 | // From HTTP p.o.v., we do not have to close after every error detected |
---|
2450 | // at the client-side, but many such errors do require closure and the |
---|
2451 | // client-side code is bad at handling errors so we play it safe. |
---|
2452 | if (request) |
---|
2453 | request->flags.proxyKeepalive = false; |
---|
2454 | flags.readMore = false; |
---|
2455 | debugs(33,4, HERE << "Will close after error: " << clientConnection); |
---|
2456 | } |
---|
2457 | |
---|
2458 | #if USE_OPENSSL |
---|
2459 | bool ConnStateData::serveDelayedError(ClientSocketContext *context) |
---|
2460 | { |
---|
2461 | ClientHttpRequest *http = context->http; |
---|
2462 | |
---|
2463 | if (!sslServerBump) |
---|
2464 | return false; |
---|
2465 | |
---|
2466 | assert(sslServerBump->entry); |
---|
2467 | // Did we create an error entry while processing CONNECT? |
---|
2468 | if (!sslServerBump->entry->isEmpty()) { |
---|
2469 | quitAfterError(http->request); |
---|
2470 | |
---|
2471 | // Get the saved error entry and send it to the client by replacing the |
---|
2472 | // ClientHttpRequest store entry with it. |
---|
2473 | clientStreamNode *node = context->getClientReplyContext(); |
---|
2474 | clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw()); |
---|
2475 | assert(repContext); |
---|
2476 | debugs(33, 5, "Responding with delated error for " << http->uri); |
---|
2477 | repContext->setReplyToStoreEntry(sslServerBump->entry, "delayed SslBump error"); |
---|
2478 | |
---|
2479 | // save the original request for logging purposes |
---|
2480 | if (!context->http->al->request) { |
---|
2481 | context->http->al->request = http->request; |
---|
2482 | HTTPMSGLOCK(context->http->al->request); |
---|
2483 | } |
---|
2484 | |
---|
2485 | // Get error details from the fake certificate-peeking request. |
---|
2486 | http->request->detailError(sslServerBump->request->errType, sslServerBump->request->errDetail); |
---|
2487 | context->pullData(); |
---|
2488 | return true; |
---|
2489 | } |
---|
2490 | |
---|
2491 | // In bump-server-first mode, we have not necessarily seen the intended |
---|
2492 | // server name at certificate-peeking time. Check for domain mismatch now, |
---|
2493 | // when we can extract the intended name from the bumped HTTP request. |
---|
2494 | if (X509 *srvCert = sslServerBump->serverCert.get()) { |
---|
2495 | HttpRequest *request = http->request; |
---|
2496 | if (!Ssl::checkX509ServerValidity(srvCert, request->GetHost())) { |
---|
2497 | debugs(33, 2, "SQUID_X509_V_ERR_DOMAIN_MISMATCH: Certificate " << |
---|
2498 | "does not match domainname " << request->GetHost()); |
---|
2499 | |
---|
2500 | bool allowDomainMismatch = false; |
---|
2501 | if (Config.ssl_client.cert_error) { |
---|
2502 | ACLFilledChecklist check(Config.ssl_client.cert_error, request, dash_str); |
---|
2503 | check.sslErrors = new Ssl::CertErrors(Ssl::CertError(SQUID_X509_V_ERR_DOMAIN_MISMATCH, srvCert)); |
---|
2504 | allowDomainMismatch = (check.fastCheck() == ACCESS_ALLOWED); |
---|
2505 | delete check.sslErrors; |
---|
2506 | check.sslErrors = NULL; |
---|
2507 | } |
---|
2508 | |
---|
2509 | if (!allowDomainMismatch) { |
---|
2510 | quitAfterError(request); |
---|
2511 | |
---|
2512 | clientStreamNode *node = context->getClientReplyContext(); |
---|
2513 | clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw()); |
---|
2514 | assert (repContext); |
---|
2515 | |
---|
2516 | // Fill the server IP and hostname for error page generation. |
---|
2517 | HttpRequest::Pointer const & peekerRequest = sslServerBump->request; |
---|
2518 | request->hier.note(peekerRequest->hier.tcpServer, request->GetHost()); |
---|
2519 | |
---|
2520 | // Create an error object and fill it |
---|
2521 | ErrorState *err = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request); |
---|
2522 | err->src_addr = clientConnection->remote; |
---|
2523 | Ssl::ErrorDetail *errDetail = new Ssl::ErrorDetail( |
---|
2524 | SQUID_X509_V_ERR_DOMAIN_MISMATCH, |
---|
2525 | srvCert, NULL); |
---|
2526 | err->detail = errDetail; |
---|
2527 | // Save the original request for logging purposes. |
---|
2528 | if (!context->http->al->request) { |
---|
2529 | context->http->al->request = request; |
---|
2530 | HTTPMSGLOCK(context->http->al->request); |
---|
2531 | } |
---|
2532 | repContext->setReplyToError(request->method, err); |
---|
2533 | assert(context->http->out.offset == 0); |
---|
2534 | context->pullData(); |
---|
2535 | return true; |
---|
2536 | } |
---|
2537 | } |
---|
2538 | } |
---|
2539 | |
---|
2540 | return false; |
---|
2541 | } |
---|
2542 | #endif // USE_OPENSSL |
---|
2543 | |
---|
2544 | static void |
---|
2545 | clientProcessRequestFinished(ConnStateData *conn, const HttpRequest::Pointer &request) |
---|
2546 | { |
---|
2547 | /* |
---|
2548 | * DPW 2007-05-18 |
---|
2549 | * Moved the TCP_RESET feature from clientReplyContext::sendMoreData |
---|
2550 | * to here because calling comm_reset_close() causes http to |
---|
2551 | * be freed and the above connNoteUseOfBuffer() would hit an |
---|
2552 | * assertion, not to mention that we were accessing freed memory. |
---|
2553 | */ |
---|
2554 | if (request != NULL && request->flags.resetTcp && Comm::IsConnOpen(conn->clientConnection)) { |
---|
2555 | debugs(33, 3, HERE << "Sending TCP RST on " << conn->clientConnection); |
---|
2556 | conn->flags.readMore = false; |
---|
2557 | comm_reset_close(conn->clientConnection); |
---|
2558 | } |
---|
2559 | } |
---|
2560 | |
---|
2561 | void |
---|
2562 | clientProcessRequest(ConnStateData *conn, HttpParser *hp, ClientSocketContext *context, const HttpRequestMethod& method, Http::ProtocolVersion http_ver) |
---|
2563 | { |
---|
2564 | ClientHttpRequest *http = context->http; |
---|
2565 | HttpRequest::Pointer request; |
---|
2566 | bool notedUseOfBuffer = false; |
---|
2567 | bool chunked = false; |
---|
2568 | bool mustReplyToOptions = false; |
---|
2569 | bool unsupportedTe = false; |
---|
2570 | bool expectBody = false; |
---|
2571 | |
---|
2572 | // temporary hack to avoid splitting this huge function with sensitive code |
---|
2573 | const bool isFtp = !hp; |
---|
2574 | if (isFtp) { |
---|
2575 | // In FTP, case, we already have the request parsed and checked, so we |
---|
2576 | // only need to go through the final body/conn setup to doCallouts(). |
---|
2577 | assert(http->request); |
---|
2578 | request = http->request; |
---|
2579 | notedUseOfBuffer = true; |
---|
2580 | } else { |
---|
2581 | |
---|
2582 | if (context->flags.parsed_ok == 0) { |
---|
2583 | clientStreamNode *node = context->getClientReplyContext(); |
---|
2584 | debugs(33, 2, "clientProcessRequest: Invalid Request"); |
---|
2585 | conn->quitAfterError(NULL); |
---|
2586 | // setLogUri should called before repContext->setReplyToError |
---|
2587 | setLogUri(http, http->uri, true); |
---|
2588 | clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw()); |
---|
2589 | assert (repContext); |
---|
2590 | switch (hp->request_parse_status) { |
---|
2591 | case Http::scHeaderTooLarge: |
---|
2592 | repContext->setReplyToError(ERR_TOO_BIG, Http::scBadRequest, method, http->uri, conn->clientConnection->remote, NULL, conn->in.buf.c_str(), NULL); |
---|
2593 | break; |
---|
2594 | case Http::scMethodNotAllowed: |
---|
2595 | repContext->setReplyToError(ERR_UNSUP_REQ, Http::scMethodNotAllowed, method, http->uri, |
---|
2596 | conn->clientConnection->remote, NULL, conn->in.buf.c_str(), NULL); |
---|
2597 | break; |
---|
2598 | default: |
---|
2599 | repContext->setReplyToError(ERR_INVALID_REQ, hp->request_parse_status, method, http->uri, |
---|
2600 | conn->clientConnection->remote, NULL, conn->in.buf.c_str(), NULL); |
---|
2601 | } |
---|
2602 | assert(context->http->out.offset == 0); |
---|
2603 | context->pullData(); |
---|
2604 | connNoteUseOfBuffer(conn, http->req_sz); |
---|
2605 | return; |
---|
2606 | } |
---|
2607 | |
---|
2608 | if ((request = HttpRequest::CreateFromUrlAndMethod(http->uri, method)) == NULL) { |
---|
2609 | clientStreamNode *node = context->getClientReplyContext(); |
---|
2610 | debugs(33, 5, "Invalid URL: " << http->uri); |
---|
2611 | conn->quitAfterError(request.getRaw()); |
---|
2612 | // setLogUri should called before repContext->setReplyToError |
---|
2613 | setLogUri(http, http->uri, true); |
---|
2614 | clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw()); |
---|
2615 | assert (repContext); |
---|
2616 | repContext->setReplyToError(ERR_INVALID_URL, Http::scBadRequest, method, http->uri, conn->clientConnection->remote, NULL, NULL, NULL); |
---|
2617 | assert(context->http->out.offset == 0); |
---|
2618 | context->pullData(); |
---|
2619 | connNoteUseOfBuffer(conn, http->req_sz); |
---|
2620 | return; |
---|
2621 | } |
---|
2622 | |
---|
2623 | /* RFC 2616 section 10.5.6 : handle unsupported HTTP major versions cleanly. */ |
---|
2624 | /* We currently only support 0.9, 1.0, 1.1 properly */ |
---|
2625 | /* TODO: move HTTP-specific processing into servers/HttpServer and such */ |
---|
2626 | if ( (http_ver.major == 0 && http_ver.minor != 9) || |
---|
2627 | (http_ver.major > 1) ) { |
---|
2628 | |
---|
2629 | clientStreamNode *node = context->getClientReplyContext(); |
---|
2630 | debugs(33, 5, "Unsupported HTTP version discovered. :\n" << HttpParserHdrBuf(hp)); |
---|
2631 | conn->quitAfterError(request.getRaw()); |
---|
2632 | // setLogUri should called before repContext->setReplyToError |
---|
2633 | setLogUri(http, http->uri, true); |
---|
2634 | clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw()); |
---|
2635 | assert (repContext); |
---|
2636 | repContext->setReplyToError(ERR_UNSUP_HTTPVERSION, Http::scHttpVersionNotSupported, method, http->uri, |
---|
2637 | conn->clientConnection->remote, NULL, HttpParserHdrBuf(hp), NULL); |
---|
2638 | assert(context->http->out.offset == 0); |
---|
2639 | context->pullData(); |
---|
2640 | connNoteUseOfBuffer(conn, http->req_sz); |
---|
2641 | clientProcessRequestFinished(conn, request); |
---|
2642 | return; |
---|
2643 | } |
---|
2644 | |
---|
2645 | /* compile headers */ |
---|
2646 | /* we should skip request line! */ |
---|
2647 | /* XXX should actually know the damned buffer size here */ |
---|
2648 | if (http_ver.major >= 1 && !request->parseHeader(HttpParserHdrBuf(hp), HttpParserHdrSz(hp))) { |
---|
2649 | clientStreamNode *node = context->getClientReplyContext(); |
---|
2650 | debugs(33, 5, "Failed to parse request headers:\n" << HttpParserHdrBuf(hp)); |
---|
2651 | conn->quitAfterError(request.getRaw()); |
---|
2652 | // setLogUri should called before repContext->setReplyToError |
---|
2653 | setLogUri(http, http->uri, true); |
---|
2654 | clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw()); |
---|
2655 | assert (repContext); |
---|
2656 | repContext->setReplyToError(ERR_INVALID_REQ, Http::scBadRequest, method, http->uri, conn->clientConnection->remote, NULL, NULL, NULL); |
---|
2657 | assert(context->http->out.offset == 0); |
---|
2658 | context->pullData(); |
---|
2659 | connNoteUseOfBuffer(conn, http->req_sz); |
---|
2660 | clientProcessRequestFinished(conn, request); |
---|
2661 | return; |
---|
2662 | } |
---|
2663 | } |
---|
2664 | |
---|
2665 | // Some blobs below are still HTTP-specific, but we would have to rewrite |
---|
2666 | // this entire function to remove them from the FTP code path. Connection |
---|
2667 | // setup and body_pipe preparation blobs are needed for FTP. |
---|
2668 | |
---|
2669 | request->clientConnectionManager = conn; |
---|
2670 | |
---|
2671 | request->flags.accelerated = http->flags.accel; |
---|
2672 | request->flags.sslBumped=conn->switchedToHttps(); |
---|
2673 | request->flags.ignoreCc = conn->port->ignore_cc; |
---|
2674 | // TODO: decouple http->flags.accel from request->flags.sslBumped |
---|
2675 | request->flags.noDirect = (request->flags.accelerated && !request->flags.sslBumped) ? |
---|
2676 | !conn->port->allow_direct : 0; |
---|
2677 | #if USE_AUTH |
---|
2678 | if (request->flags.sslBumped) { |
---|
2679 | if (conn->getAuth() != NULL) |
---|
2680 | request->auth_user_request = conn->getAuth(); |
---|
2681 | } |
---|
2682 | #endif |
---|
2683 | |
---|
2684 | /** \par |
---|
2685 | * If transparent or interception mode is working clone the transparent and interception flags |
---|
2686 | * from the port settings to the request. |
---|
2687 | */ |
---|
2688 | if (http->clientConnection != NULL) { |
---|
2689 | request->flags.intercepted = ((http->clientConnection->flags & COMM_INTERCEPTION) != 0); |
---|
2690 | request->flags.interceptTproxy = ((http->clientConnection->flags & COMM_TRANSPARENT) != 0 ) ; |
---|
2691 | static const bool proxyProtocolPort = (conn->port != NULL) ? conn->port->flags.proxySurrogate : false; |
---|
2692 | if (request->flags.interceptTproxy && !proxyProtocolPort) { |
---|
2693 | if (Config.accessList.spoof_client_ip) { |
---|
2694 | ACLFilledChecklist *checklist = clientAclChecklistCreate(Config.accessList.spoof_client_ip, http); |
---|
2695 | request->flags.spoofClientIp = (checklist->fastCheck() == ACCESS_ALLOWED); |
---|
2696 | delete checklist; |
---|
2697 | } else |
---|
2698 | request->flags.spoofClientIp = true; |
---|
2699 | } else |
---|
2700 | request->flags.spoofClientIp = false; |
---|
2701 | } |
---|
2702 | |
---|
2703 | if (internalCheck(request->urlpath.termedBuf())) { |
---|
2704 | if (internalHostnameIs(request->GetHost()) && request->port == getMyPort()) { |
---|
2705 | debugs(33, 2, "internal URL found: " << request->url.getScheme() << "://" << request->GetHost() << |
---|
2706 | ':' << request->port); |
---|
2707 | http->flags.internal = true; |
---|
2708 | } else if (Config.onoff.global_internal_static && internalStaticCheck(request->urlpath.termedBuf())) { |
---|
2709 | debugs(33, 2, "internal URL found: " << request->url.getScheme() << "://" << request->GetHost() << |
---|
2710 | ':' << request->port << " (global_internal_static on)"); |
---|
2711 | request->url.setScheme(AnyP::PROTO_HTTP); |
---|
2712 | request->SetHost(internalHostname()); |
---|
2713 | request->port = getMyPort(); |
---|
2714 | http->flags.internal = true; |
---|
2715 | } else |
---|
2716 | debugs(33, 2, "internal URL found: " << request->url.getScheme() << "://" << request->GetHost() << |
---|
2717 | ':' << request->port << " (not this proxy)"); |
---|
2718 | } |
---|
2719 | |
---|
2720 | if (http->flags.internal) |
---|
2721 | request->login[0] = '\0'; |
---|
2722 | |
---|
2723 | request->flags.internal = http->flags.internal; |
---|
2724 | setLogUri (http, urlCanonicalClean(request.getRaw())); |
---|
2725 | request->client_addr = conn->clientConnection->remote; // XXX: remove reuest->client_addr member. |
---|
2726 | #if FOLLOW_X_FORWARDED_FOR |
---|
2727 | // indirect client gets stored here because it is an HTTP header result (from X-Forwarded-For:) |
---|
2728 | // not a details about teh TCP connection itself |
---|
2729 | request->indirect_client_addr = conn->clientConnection->remote; |
---|
2730 | #endif /* FOLLOW_X_FORWARDED_FOR */ |
---|
2731 | request->my_addr = conn->clientConnection->local; |
---|
2732 | request->myportname = conn->port->name; |
---|
2733 | request->http_ver = http_ver; |
---|
2734 | |
---|
2735 | // Link this HttpRequest to ConnStateData relatively early so the following complex handling can use it |
---|
2736 | // TODO: this effectively obsoletes a lot of conn->FOO copying. That needs cleaning up later. |
---|
2737 | request->clientConnectionManager = conn; |
---|
2738 | |
---|
2739 | if (request->header.chunked()) { |
---|
2740 | chunked = true; |
---|
2741 | } else if (request->header.has(HDR_TRANSFER_ENCODING)) { |
---|
2742 | const String te = request->header.getList(HDR_TRANSFER_ENCODING); |
---|
2743 | // HTTP/1.1 requires chunking to be the last encoding if there is one |
---|
2744 | unsupportedTe = te.size() && te != "identity"; |
---|
2745 | } // else implied identity coding |
---|
2746 | |
---|
2747 | mustReplyToOptions = (method == Http::METHOD_OPTIONS) && |
---|
2748 | (request->header.getInt64(HDR_MAX_FORWARDS) == 0); |
---|
2749 | if (!urlCheckRequest(request.getRaw()) || mustReplyToOptions || unsupportedTe) { |
---|
2750 | clientStreamNode *node = context->getClientReplyContext(); |
---|
2751 | conn->quitAfterError(request.getRaw()); |
---|
2752 | clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw()); |
---|
2753 | assert (repContext); |
---|
2754 | repContext->setReplyToError(ERR_UNSUP_REQ, Http::scNotImplemented, request->method, NULL, |
---|
2755 | conn->clientConnection->remote, request.getRaw(), NULL, NULL); |
---|
2756 | assert(context->http->out.offset == 0); |
---|
2757 | context->pullData(); |
---|
2758 | connNoteUseOfBuffer(conn, http->req_sz); |
---|
2759 | clientProcessRequestFinished(conn, request); |
---|
2760 | return; |
---|
2761 | } |
---|
2762 | |
---|
2763 | if (!chunked && !clientIsContentLengthValid(request.getRaw())) { |
---|
2764 | clientStreamNode *node = context->getClientReplyContext(); |
---|
2765 | clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw()); |
---|
2766 | assert (repContext); |
---|
2767 | conn->quitAfterError(request.getRaw()); |
---|
2768 | repContext->setReplyToError(ERR_INVALID_REQ, |
---|
2769 | Http::scLengthRequired, request->method, NULL, |
---|
2770 | conn->clientConnection->remote, request.getRaw(), NULL, NULL); |
---|
2771 | assert(context->http->out.offset == 0); |
---|
2772 | context->pullData(); |
---|
2773 | connNoteUseOfBuffer(conn, http->req_sz); |
---|
2774 | clientProcessRequestFinished(conn, request); |
---|
2775 | return; |
---|
2776 | } |
---|
2777 | |
---|
2778 | if (request->header.has(HDR_EXPECT)) { |
---|
2779 | const String expect = request->header.getList(HDR_EXPECT); |
---|
2780 | const bool supportedExpect = (expect.caseCmp("100-continue") == 0); |
---|
2781 | if (!supportedExpect) { |
---|
2782 | clientStreamNode *node = context->getClientReplyContext(); |
---|
2783 | clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw()); |
---|
2784 | assert (repContext); |
---|
2785 | conn->quitAfterError(request.getRaw()); |
---|
2786 | repContext->setReplyToError(ERR_INVALID_REQ, Http::scExpectationFailed, request->method, http->uri, |
---|
2787 | conn->clientConnection->remote, request.getRaw(), NULL, NULL); |
---|
2788 | assert(context->http->out.offset == 0); |
---|
2789 | context->pullData(); |
---|
2790 | connNoteUseOfBuffer(conn, http->req_sz); |
---|
2791 | clientProcessRequestFinished(conn, request); |
---|
2792 | return; |
---|
2793 | } |
---|
2794 | } |
---|
2795 | |
---|
2796 | if (!isFtp) { |
---|
2797 | http->request = request.getRaw(); |
---|
2798 | HTTPMSGLOCK(http->request); |
---|
2799 | } |
---|
2800 | |
---|
2801 | clientSetKeepaliveFlag(http); |
---|
2802 | // Let tunneling code be fully responsible for CONNECT requests |
---|
2803 | if (http->request->method == Http::METHOD_CONNECT) { |
---|
2804 | context->mayUseConnection(true); |
---|
2805 | conn->flags.readMore = false; |
---|
2806 | |
---|
2807 | // consume header early so that tunnel gets just the body |
---|
2808 | connNoteUseOfBuffer(conn, http->req_sz); |
---|
2809 | notedUseOfBuffer = true; |
---|
2810 | } |
---|
2811 | |
---|
2812 | #if USE_OPENSSL |
---|
2813 | if (conn->switchedToHttps() && conn->serveDelayedError(context)) { |
---|
2814 | if (!notedUseOfBuffer) |
---|
2815 | connNoteUseOfBuffer(conn, http->req_sz); |
---|
2816 | clientProcessRequestFinished(conn, request); |
---|
2817 | return; |
---|
2818 | } |
---|
2819 | #endif |
---|
2820 | |
---|
2821 | /* Do we expect a request-body? */ |
---|
2822 | expectBody = chunked || request->content_length > 0; |
---|
2823 | if (!context->mayUseConnection() && expectBody) { |
---|
2824 | request->body_pipe = conn->expectRequestBody( |
---|
2825 | chunked ? -1 : request->content_length); |
---|
2826 | |
---|
2827 | if (!isFtp) { |
---|
2828 | // consume header early so that body pipe gets just the body |
---|
2829 | connNoteUseOfBuffer(conn, http->req_sz); |
---|
2830 | notedUseOfBuffer = true; |
---|
2831 | } |
---|
2832 | |
---|
2833 | /* Is it too large? */ |
---|
2834 | if (!chunked && // if chunked, we will check as we accumulate |
---|
2835 | clientIsRequestBodyTooLargeForPolicy(request->content_length)) { |
---|
2836 | clientStreamNode *node = context->getClientReplyContext(); |
---|
2837 | clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw()); |
---|
2838 | assert (repContext); |
---|
2839 | conn->quitAfterError(request.getRaw()); |
---|
2840 | repContext->setReplyToError(ERR_TOO_BIG, |
---|
2841 | Http::scPayloadTooLarge, Http::METHOD_NONE, NULL, |
---|
2842 | conn->clientConnection->remote, http->request, NULL, NULL); |
---|
2843 | assert(context->http->out.offset == 0); |
---|
2844 | context->pullData(); |
---|
2845 | clientProcessRequestFinished(conn, request); |
---|
2846 | return; |
---|
2847 | } |
---|
2848 | |
---|
2849 | if (!isFtp) { |
---|
2850 | // We may stop producing, comm_close, and/or call setReplyToError() |
---|
2851 | // below, so quit on errors to avoid http->doCallouts() |
---|
2852 | if (!conn->handleRequestBodyData()) { |
---|
2853 | clientProcessRequestFinished(conn, request); |
---|
2854 | return; |
---|
2855 | } |
---|
2856 | |
---|
2857 | if (!request->body_pipe->productionEnded()) { |
---|
2858 | debugs(33, 5, "need more request body"); |
---|
2859 | context->mayUseConnection(true); |
---|
2860 | assert(conn->flags.readMore); |
---|
2861 | } |
---|
2862 | } |
---|
2863 | } |
---|
2864 | |
---|
2865 | http->calloutContext = new ClientRequestContext(http); |
---|
2866 | |
---|
2867 | http->doCallouts(); |
---|
2868 | |
---|
2869 | if (!notedUseOfBuffer) |
---|
2870 | connNoteUseOfBuffer(conn, http->req_sz); |
---|
2871 | |
---|
2872 | clientProcessRequestFinished(conn, request); |
---|
2873 | } |
---|
2874 | |
---|
2875 | static void |
---|
2876 | connStripBufferWhitespace (ConnStateData * conn) |
---|
2877 | { |
---|
2878 | // XXX: kill this whole function. |
---|
2879 | while (!conn->in.buf.isEmpty() && xisspace(conn->in.buf.at(0))) { |
---|
2880 | conn->in.buf.consume(1); |
---|
2881 | } |
---|
2882 | } |
---|
2883 | |
---|
2884 | int |
---|
2885 | ConnStateData::pipelinePrefetchMax() const |
---|
2886 | { |
---|
2887 | // TODO: Support pipelined requests through pinned connections. |
---|
2888 | if (pinning.pinned) |
---|
2889 | return 0; |
---|
2890 | return Config.pipeline_max_prefetch; |
---|
2891 | } |
---|
2892 | |
---|
2893 | /** |
---|
2894 | * Limit the number of concurrent requests. |
---|
2895 | * \return true when there are available position(s) in the pipeline queue for another request. |
---|
2896 | * \return false when the pipeline queue is full or disabled. |
---|
2897 | */ |
---|
2898 | bool |
---|
2899 | ConnStateData::concurrentRequestQueueFilled() const |
---|
2900 | { |
---|
2901 | const int existingRequestCount = getConcurrentRequestCount(); |
---|
2902 | |
---|
2903 | // default to the configured pipeline size. |
---|
2904 | // add 1 because the head of pipeline is counted in concurrent requests and not prefetch queue |
---|
2905 | #if USE_OPENSSL |
---|
2906 | const int internalRequest = (transparent() && sslBumpMode == Ssl::bumpSplice) ? 1 : 0; |
---|
2907 | #else |
---|
2908 | const int internalRequest = 0; |
---|
2909 | #endif |
---|
2910 | const int concurrentRequestLimit = pipelinePrefetchMax() + 1 + internalRequest; |
---|
2911 | |
---|
2912 | // when queue filled already we cant add more. |
---|
2913 | if (existingRequestCount >= concurrentRequestLimit) { |
---|
2914 | debugs(33, 3, clientConnection << " max concurrent requests reached (" << concurrentRequestLimit << ")"); |
---|
2915 | debugs(33, 5, clientConnection << " deferring new request until one is done"); |
---|
2916 | return true; |
---|
2917 | } |
---|
2918 | |
---|
2919 | return false; |
---|
2920 | } |
---|
2921 | |
---|
2922 | /** |
---|
2923 | * Perform proxy_protocol_access ACL tests on the client which |
---|
2924 | * connected to PROXY protocol port to see if we trust the |
---|
2925 | * sender enough to accept their PROXY header claim. |
---|
2926 | */ |
---|
2927 | bool |
---|
2928 | ConnStateData::proxyProtocolValidateClient() |
---|
2929 | { |
---|
2930 | if (!Config.accessList.proxyProtocol) |
---|
2931 | return proxyProtocolError("PROXY client not permitted by default ACL"); |
---|
2932 | |
---|
2933 | ACLFilledChecklist ch(Config.accessList.proxyProtocol, NULL, clientConnection->rfc931); |
---|
2934 | ch.src_addr = clientConnection->remote; |
---|
2935 | ch.my_addr = clientConnection->local; |
---|
2936 | ch.conn(this); |
---|
2937 | |
---|
2938 | if (ch.fastCheck() != ACCESS_ALLOWED) |
---|
2939 | return proxyProtocolError("PROXY client not permitted by ACLs"); |
---|
2940 | |
---|
2941 | return true; |
---|
2942 | } |
---|
2943 | |
---|
2944 | /** |
---|
2945 | * Perform cleanup on PROXY protocol errors. |
---|
2946 | * If header parsing hits a fatal error terminate the connection, |
---|
2947 | * otherwise wait for more data. |
---|
2948 | */ |
---|
2949 | bool |
---|
2950 | ConnStateData::proxyProtocolError(const char *msg) |
---|
2951 | { |
---|
2952 | if (msg) { |
---|
2953 | // This is important to know, but maybe not so much that flooding the log is okay. |
---|
2954 | #if QUIET_PROXY_PROTOCOL |
---|
2955 | // display the first of every 32 occurances at level 1, the others at level 2. |
---|
2956 | static uint8_t hide = 0; |
---|
2957 | debugs(33, (hide++ % 32 == 0 ? DBG_IMPORTANT : 2), msg << " from " << clientConnection); |
---|
2958 | #else |
---|
2959 | debugs(33, DBG_IMPORTANT, msg << " from " << clientConnection); |
---|
2960 | #endif |
---|
2961 | mustStop(msg); |
---|
2962 | } |
---|
2963 | return false; |
---|
2964 | } |
---|
2965 | |
---|
2966 | /// magic octet prefix for PROXY protocol version 1 |
---|
2967 | static const SBuf Proxy1p0magic("PROXY ", 6); |
---|
2968 | |
---|
2969 | /// magic octet prefix for PROXY protocol version 2 |
---|
2970 | static const SBuf Proxy2p0magic("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12); |
---|
2971 | |
---|
2972 | /** |
---|
2973 | * Test the connection read buffer for PROXY protocol header. |
---|
2974 | * Version 1 and 2 header currently supported. |
---|
2975 | */ |
---|
2976 | bool |
---|
2977 | ConnStateData::parseProxyProtocolHeader() |
---|
2978 | { |
---|
2979 | // http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt |
---|
2980 | |
---|
2981 | // detect and parse PROXY/2.0 protocol header |
---|
2982 | if (in.buf.startsWith(Proxy2p0magic)) |
---|
2983 | return parseProxy2p0(); |
---|
2984 | |
---|
2985 | // detect and parse PROXY/1.0 protocol header |
---|
2986 | if (in.buf.startsWith(Proxy1p0magic)) |
---|
2987 | return parseProxy1p0(); |
---|
2988 | |
---|
2989 | // detect and terminate other protocols |
---|
2990 | if (in.buf.length() >= Proxy2p0magic.length()) { |
---|
2991 | // PROXY/1.0 magic is shorter, so we know that |
---|
2992 | // the input does not start with any PROXY magic |
---|
2993 | return proxyProtocolError("PROXY protocol error: invalid header"); |
---|
2994 | } |
---|
2995 | |
---|
2996 | // TODO: detect short non-magic prefixes earlier to avoid |
---|
2997 | // waiting for more data which may never come |
---|
2998 | |
---|
2999 | // not enough bytes to parse yet. |
---|
3000 | return false; |
---|
3001 | } |
---|
3002 | |
---|
3003 | /// parse the PROXY/1.0 protocol header from the connection read buffer |
---|
3004 | bool |
---|
3005 | ConnStateData::parseProxy1p0() |
---|
3006 | { |
---|
3007 | ::Parser::Tokenizer tok(in.buf); |
---|
3008 | tok.skip(Proxy1p0magic); |
---|
3009 | |
---|
3010 | // skip to first LF (assumes it is part of CRLF) |
---|
3011 | static const CharacterSet lineContent = CharacterSet::LF.complement("non-LF"); |
---|
3012 | SBuf line; |
---|
3013 | if (tok.prefix(line, lineContent, 107-Proxy1p0magic.length())) { |
---|
3014 | if (tok.skip('\n')) { |
---|
3015 | // found valid header |
---|
3016 | in.buf = tok.remaining(); |
---|
3017 | needProxyProtocolHeader_ = false; |
---|
3018 | // reset the tokenizer to work on found line only. |
---|
3019 | tok.reset(line); |
---|
3020 | } else |
---|
3021 | return false; // no LF yet |
---|
3022 | |
---|
3023 | } else // protocol error only if there are more than 107 bytes prefix header |
---|
3024 | return proxyProtocolError(in.buf.length() > 107? "PROXY/1.0 error: missing CRLF" : NULL); |
---|
3025 | |
---|
3026 | static const SBuf unknown("UNKNOWN"), tcpName("TCP"); |
---|
3027 | if (tok.skip(tcpName)) { |
---|
3028 | |
---|
3029 | // skip TCP/IP version number |
---|
3030 | static const CharacterSet tcpVersions("TCP-version","46"); |
---|
3031 | if (!tok.skipOne(tcpVersions)) |
---|
3032 | return proxyProtocolError("PROXY/1.0 error: missing TCP version"); |
---|
3033 | |
---|
3034 | // skip SP after protocol version |
---|
3035 | if (!tok.skip(' ')) |
---|
3036 | return proxyProtocolError("PROXY/1.0 error: missing SP"); |
---|
3037 | |
---|
3038 | SBuf ipa, ipb; |
---|
3039 | int64_t porta, portb; |
---|
3040 | static const CharacterSet ipChars = CharacterSet("IP Address",".:") + CharacterSet::HEXDIG; |
---|
3041 | |
---|
3042 | // parse: src-IP SP dst-IP SP src-port SP dst-port CR |
---|
3043 | // leave the LF until later. |
---|
3044 | const bool correct = tok.prefix(ipa, ipChars) && tok.skip(' ') && |
---|
3045 | tok.prefix(ipb, ipChars) && tok.skip(' ') && |
---|
3046 | tok.int64(porta) && tok.skip(' ') && |
---|
3047 | tok.int64(portb) && |
---|
3048 | tok.skip('\r'); |
---|
3049 | if (!correct) |
---|
3050 | return proxyProtocolError("PROXY/1.0 error: invalid syntax"); |
---|
3051 | |
---|
3052 | // parse IP and port strings |
---|
3053 | Ip::Address originalClient, originalDest; |
---|
3054 | |
---|
3055 | if (!originalClient.GetHostByName(ipa.c_str())) |
---|
3056 | return proxyProtocolError("PROXY/1.0 error: invalid src-IP address"); |
---|
3057 | |
---|
3058 | if (!originalDest.GetHostByName(ipb.c_str())) |
---|
3059 | return proxyProtocolError("PROXY/1.0 error: invalid dst-IP address"); |
---|
3060 | |
---|
3061 | if (porta > 0 && porta <= 0xFFFF) // max uint16_t |
---|
3062 | originalClient.port(static_cast<uint16_t>(porta)); |
---|
3063 | else |
---|
3064 | return proxyProtocolError("PROXY/1.0 error: invalid src port"); |
---|
3065 | |
---|
3066 | if (portb > 0 && portb <= 0xFFFF) // max uint16_t |
---|
3067 | originalDest.port(static_cast<uint16_t>(portb)); |
---|
3068 | else |
---|
3069 | return proxyProtocolError("PROXY/1.0 error: invalid dst port"); |
---|
3070 | |
---|
3071 | // we have original client and destination details now |
---|
3072 | // replace the client connection values |
---|
3073 | debugs(33, 5, "PROXY/1.0 protocol on connection " << clientConnection); |
---|
3074 | clientConnection->local = originalDest; |
---|
3075 | clientConnection->remote = originalClient; |
---|
3076 | if ((clientConnection->flags & COMM_TRANSPARENT)) |
---|
3077 | clientConnection->flags ^= COMM_TRANSPARENT; // prevent TPROXY spoofing of this new IP. |
---|
3078 | debugs(33, 5, "PROXY/1.0 upgrade: " << clientConnection); |
---|
3079 | |
---|
3080 | // repeat fetch ensuring the new client FQDN can be logged |
---|
3081 | if (Config.onoff.log_fqdn) |
---|
3082 | fqdncache_gethostbyaddr(clientConnection->remote, FQDN_LOOKUP_IF_MISS); |
---|
3083 | |
---|
3084 | return true; |
---|
3085 | |
---|
3086 | } else if (tok.skip(unknown)) { |
---|
3087 | // found valid but unusable header |
---|
3088 | return true; |
---|
3089 | |
---|
3090 | } else |
---|
3091 | return proxyProtocolError("PROXY/1.0 error: invalid protocol family"); |
---|
3092 | |
---|
3093 | return false; |
---|
3094 | } |
---|
3095 | |
---|
3096 | /// parse the PROXY/2.0 protocol header from the connection read buffer |
---|
3097 | bool |
---|
3098 | ConnStateData::parseProxy2p0() |
---|
3099 | { |
---|
3100 | static const SBuf::size_type prefixLen = Proxy2p0magic.length(); |
---|
3101 | if (in.buf.length() < prefixLen + 4) |
---|
3102 | return false; // need more bytes |
---|
3103 | |
---|
3104 | if ((in.buf[prefixLen] & 0xF0) != 0x20) // version == 2 is mandatory |
---|
3105 | return proxyProtocolError("PROXY/2.0 error: invalid version"); |
---|
3106 | |
---|
3107 | const char command = (in.buf[prefixLen] & 0x0F); |
---|
3108 | if ((command & 0xFE) != 0x00) // values other than 0x0-0x1 are invalid |
---|
3109 | return proxyProtocolError("PROXY/2.0 error: invalid command"); |
---|
3110 | |
---|
3111 | const char family = (in.buf[prefixLen+1] & 0xF0) >>4; |
---|
3112 | if (family > 0x3) // values other than 0x0-0x3 are invalid |
---|
3113 | return proxyProtocolError("PROXY/2.0 error: invalid family"); |
---|
3114 | |
---|
3115 | const char proto = (in.buf[prefixLen+1] & 0x0F); |
---|
3116 | if (proto > 0x2) // values other than 0x0-0x2 are invalid |
---|
3117 | return proxyProtocolError("PROXY/2.0 error: invalid protocol type"); |
---|
3118 | |
---|
3119 | const char *clen = in.buf.rawContent() + prefixLen + 2; |
---|
3120 | uint16_t len; |
---|
3121 | memcpy(&len, clen, sizeof(len)); |
---|
3122 | len = ntohs(len); |
---|
3123 | |
---|
3124 | if (in.buf.length() < prefixLen + 4 + len) |
---|
3125 | return false; // need more bytes |
---|
3126 | |
---|
3127 | in.buf.consume(prefixLen + 4); // 4 being the extra bytes |
---|
3128 | const SBuf extra = in.buf.consume(len); |
---|
3129 | needProxyProtocolHeader_ = false; // found successfully |
---|
3130 | |
---|
3131 | // LOCAL connections do nothing with the extras |
---|
3132 | if (command == 0x00/* LOCAL*/) |
---|
3133 | return true; |
---|
3134 | |
---|
3135 | union pax { |
---|
3136 | struct { /* for TCP/UDP over IPv4, len = 12 */ |
---|
3137 | struct in_addr src_addr; |
---|
3138 | struct in_addr dst_addr; |
---|
3139 | uint16_t src_port; |
---|
3140 | uint16_t dst_port; |
---|
3141 | } ipv4_addr; |
---|
3142 | struct { /* for TCP/UDP over IPv6, len = 36 */ |
---|
3143 | struct in6_addr src_addr; |
---|
3144 | struct in6_addr dst_addr; |
---|
3145 | uint16_t src_port; |
---|
3146 | uint16_t dst_port; |
---|
3147 | } ipv6_addr; |
---|
3148 | #if NOT_SUPPORTED |
---|
3149 | struct { /* for AF_UNIX sockets, len = 216 */ |
---|
3150 | uint8_t src_addr[108]; |
---|
3151 | uint8_t dst_addr[108]; |
---|
3152 | } unix_addr; |
---|
3153 | #endif |
---|
3154 | }; |
---|
3155 | |
---|
3156 | pax ipu; |
---|
3157 | memcpy(&ipu, extra.rawContent(), sizeof(pax)); |
---|
3158 | |
---|
3159 | // replace the client connection values |
---|
3160 | debugs(33, 5, "PROXY/2.0 protocol on connection " << clientConnection); |
---|
3161 | switch (family) { |
---|
3162 | case 0x1: // IPv4 |
---|
3163 | clientConnection->local = ipu.ipv4_addr.dst_addr; |
---|
3164 | clientConnection->local.port(ntohs(ipu.ipv4_addr.dst_port)); |
---|
3165 | clientConnection->remote = ipu.ipv4_addr.src_addr; |
---|
3166 | clientConnection->remote.port(ntohs(ipu.ipv4_addr.src_port)); |
---|
3167 | if ((clientConnection->flags & COMM_TRANSPARENT)) |
---|
3168 | clientConnection->flags ^= COMM_TRANSPARENT; // prevent TPROXY spoofing of this new IP. |
---|
3169 | break; |
---|
3170 | case 0x2: // IPv6 |
---|
3171 | clientConnection->local = ipu.ipv6_addr.dst_addr; |
---|
3172 | clientConnection->local.port(ntohs(ipu.ipv6_addr.dst_port)); |
---|
3173 | clientConnection->remote = ipu.ipv6_addr.src_addr; |
---|
3174 | clientConnection->remote.port(ntohs(ipu.ipv6_addr.src_port)); |
---|
3175 | if ((clientConnection->flags & COMM_TRANSPARENT)) |
---|
3176 | clientConnection->flags ^= COMM_TRANSPARENT; // prevent TPROXY spoofing of this new IP. |
---|
3177 | break; |
---|
3178 | default: // do nothing |
---|
3179 | break; |
---|
3180 | } |
---|
3181 | debugs(33, 5, "PROXY/2.0 upgrade: " << clientConnection); |
---|
3182 | |
---|
3183 | // repeat fetch ensuring the new client FQDN can be logged |
---|
3184 | if (Config.onoff.log_fqdn) |
---|
3185 | fqdncache_gethostbyaddr(clientConnection->remote, FQDN_LOOKUP_IF_MISS); |
---|
3186 | |
---|
3187 | return true; |
---|
3188 | } |
---|
3189 | |
---|
3190 | /** |
---|
3191 | * Attempt to parse one or more requests from the input buffer. |
---|
3192 | * Returns true after completing parsing of at least one request [header]. That |
---|
3193 | * includes cases where parsing ended with an error (e.g., a huge request). |
---|
3194 | */ |
---|
3195 | bool |
---|
3196 | ConnStateData::clientParseRequests() |
---|
3197 | { |
---|
3198 | bool parsed_req = false; |
---|
3199 | |
---|
3200 | debugs(33, 5, HERE << clientConnection << ": attempting to parse"); |
---|
3201 | |
---|
3202 | // Loop while we have read bytes that are not needed for producing the body |
---|
3203 | // On errors, bodyPipe may become nil, but readMore will be cleared |
---|
3204 | while (!in.buf.isEmpty() && !bodyPipe && flags.readMore) { |
---|
3205 | connStripBufferWhitespace(this); |
---|
3206 | |
---|
3207 | /* Don't try to parse if the buffer is empty */ |
---|
3208 | if (in.buf.isEmpty()) |
---|
3209 | break; |
---|
3210 | |
---|
3211 | /* Limit the number of concurrent requests */ |
---|
3212 | if (concurrentRequestQueueFilled()) |
---|
3213 | break; |
---|
3214 | |
---|
3215 | // try to parse the PROXY protocol header magic bytes |
---|
3216 | if (needProxyProtocolHeader_ && !parseProxyProtocolHeader()) |
---|
3217 | break; |
---|
3218 | |
---|
3219 | Http::ProtocolVersion http_ver; |
---|
3220 | if (ClientSocketContext *context = parseOneRequest(http_ver)) { |
---|
3221 | debugs(33, 5, clientConnection << ": done parsing a request"); |
---|
3222 | AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "clientLifetimeTimeout", |
---|
3223 | CommTimeoutCbPtrFun(clientLifetimeTimeout, context->http)); |
---|
3224 | commSetConnTimeout(clientConnection, Config.Timeout.lifetime, timeoutCall); |
---|
3225 | |
---|
3226 | context->registerWithConn(); |
---|
3227 | |
---|
3228 | processParsedRequest(context, http_ver); |
---|
3229 | |
---|
3230 | parsed_req = true; // XXX: do we really need to parse everything right NOW ? |
---|
3231 | |
---|
3232 | if (context->mayUseConnection()) { |
---|
3233 | debugs(33, 3, HERE << "Not parsing new requests, as this request may need the connection"); |
---|
3234 | break; |
---|
3235 | } |
---|
3236 | } else { |
---|
3237 | debugs(33, 5, clientConnection << ": not enough request data: " << |
---|
3238 | in.buf.length() << " < " << Config.maxRequestHeaderSize); |
---|
3239 | Must(in.buf.length() < Config.maxRequestHeaderSize); |
---|
3240 | break; |
---|
3241 | } |
---|
3242 | } |
---|
3243 | |
---|
3244 | /* XXX where to 'finish' the parsing pass? */ |
---|
3245 | return parsed_req; |
---|
3246 | } |
---|
3247 | |
---|
3248 | void |
---|
3249 | ConnStateData::clientReadRequest(const CommIoCbParams &io) |
---|
3250 | { |
---|
3251 | debugs(33,5, io.conn); |
---|
3252 | Must(reading()); |
---|
3253 | reader = NULL; |
---|
3254 | |
---|
3255 | /* Bail out quickly on Comm::ERR_CLOSING - close handlers will tidy up */ |
---|
3256 | if (io.flag == Comm::ERR_CLOSING) { |
---|
3257 | debugs(33,5, io.conn << " closing Bailout."); |
---|
3258 | return; |
---|
3259 | } |
---|
3260 | |
---|
3261 | assert(Comm::IsConnOpen(clientConnection)); |
---|
3262 | assert(io.conn->fd == clientConnection->fd); |
---|
3263 | |
---|
3264 | /* |
---|
3265 | * Don't reset the timeout value here. The value should be |
---|
3266 | * counting Config.Timeout.request and applies to the request |
---|
3267 | * as a whole, not individual read() calls. |
---|
3268 | * Plus, it breaks our lame *HalfClosed() detection |
---|
3269 | */ |
---|
3270 | |
---|
3271 | in.maybeMakeSpaceAvailable(); |
---|
3272 | CommIoCbParams rd(this); // will be expanded with ReadNow results |
---|
3273 | rd.conn = io.conn; |
---|
3274 | switch (Comm::ReadNow(rd, in.buf)) { |
---|
3275 | case Comm::INPROGRESS: |
---|
3276 | if (in.buf.isEmpty()) |
---|
3277 | debugs(33, 2, io.conn << ": no data to process, " << xstrerr(rd.xerrno)); |
---|
3278 | readSomeData(); |
---|
3279 | return; |
---|
3280 | |
---|
3281 | case Comm::OK: |
---|
3282 | kb_incr(&(statCounter.client_http.kbytes_in), rd.size); |
---|
3283 | // may comm_close or setReplyToError |
---|
3284 | if (!handleReadData()) |
---|
3285 | return; |
---|
3286 | |
---|
3287 | /* Continue to process previously read data */ |
---|
3288 | break; |
---|
3289 | |
---|
3290 | case Comm::ENDFILE: // close detected by 0-byte read |
---|
3291 | debugs(33, 5, io.conn << " closed?"); |
---|
3292 | |
---|
3293 | if (connFinishedWithConn(rd.size)) { |
---|
3294 | clientConnection->close(); |
---|
3295 | return; |
---|
3296 | } |
---|
3297 | |
---|
3298 | /* It might be half-closed, we can't tell */ |
---|
3299 | fd_table[io.conn->fd].flags.socket_eof = true; |
---|
3300 | commMarkHalfClosed(io.conn->fd); |
---|
3301 | fd_note(io.conn->fd, "half-closed"); |
---|
3302 | |
---|
3303 | /* There is one more close check at the end, to detect aborted |
---|
3304 | * (partial) requests. At this point we can't tell if the request |
---|
3305 | * is partial. |
---|
3306 | */ |
---|
3307 | |
---|
3308 | /* Continue to process previously read data */ |
---|
3309 | break; |
---|
3310 | |
---|
3311 | // case Comm::COMM_ERROR: |
---|
3312 | default: // no other flags should ever occur |
---|
3313 | debugs(33, 2, io.conn << ": got flag " << rd.flag << "; " << xstrerr(rd.xerrno)); |
---|
3314 | notifyAllContexts(rd.xerrno); |
---|
3315 | io.conn->close(); |
---|
3316 | return; |
---|
3317 | } |
---|
3318 | |
---|
3319 | /* Process next request */ |
---|
3320 | if (getConcurrentRequestCount() == 0) |
---|
3321 | fd_note(io.fd, "Reading next request"); |
---|
3322 | |
---|
3323 | if (!clientParseRequests()) { |
---|
3324 | if (!isOpen()) |
---|
3325 | return; |
---|
3326 | /* |
---|
3327 | * If the client here is half closed and we failed |
---|
3328 | * to parse a request, close the connection. |
---|
3329 | * The above check with connFinishedWithConn() only |
---|
3330 | * succeeds _if_ the buffer is empty which it won't |
---|
3331 | * be if we have an incomplete request. |
---|
3332 | * XXX: This duplicates ClientSocketContext::keepaliveNextRequest |
---|
3333 | */ |
---|
3334 | if (getConcurrentRequestCount() == 0 && commIsHalfClosed(io.fd)) { |
---|
3335 | debugs(33, 5, HERE << io.conn << ": half-closed connection, no completed request parsed, connection closing."); |
---|
3336 | clientConnection->close(); |
---|
3337 | return; |
---|
3338 | } |
---|
3339 | } |
---|
3340 | |
---|
3341 | if (!isOpen()) |
---|
3342 | return; |
---|
3343 | |
---|
3344 | clientAfterReadingRequests(); |
---|
3345 | } |
---|
3346 | |
---|
3347 | /** |
---|
3348 | * called when new request data has been read from the socket |
---|
3349 | * |
---|
3350 | * \retval false called comm_close or setReplyToError (the caller should bail) |
---|
3351 | * \retval true we did not call comm_close or setReplyToError |
---|
3352 | */ |
---|
3353 | bool |
---|
3354 | ConnStateData::handleReadData() |
---|
3355 | { |
---|
3356 | // if we are reading a body, stuff data into the body pipe |
---|
3357 | if (bodyPipe != NULL) |
---|
3358 | return handleRequestBodyData(); |
---|
3359 | return true; |
---|
3360 | } |
---|
3361 | |
---|
3362 | /** |
---|
3363 | * called when new request body data has been buffered in in.buf |
---|
3364 | * may close the connection if we were closing and piped everything out |
---|
3365 | * |
---|
3366 | * \retval false called comm_close or setReplyToError (the caller should bail) |
---|
3367 | * \retval true we did not call comm_close or setReplyToError |
---|
3368 | */ |
---|
3369 | bool |
---|
3370 | ConnStateData::handleRequestBodyData() |
---|
3371 | { |
---|
3372 | assert(bodyPipe != NULL); |
---|
3373 | |
---|
3374 | size_t putSize = 0; |
---|
3375 | |
---|
3376 | if (in.bodyParser) { // chunked encoding |
---|
3377 | if (const err_type error = handleChunkedRequestBody(putSize)) { |
---|
3378 | abortChunkedRequestBody(error); |
---|
3379 | return false; |
---|
3380 | } |
---|
3381 | } else { // identity encoding |
---|
3382 | debugs(33,5, HERE << "handling plain request body for " << clientConnection); |
---|
3383 | putSize = bodyPipe->putMoreData(in.buf.c_str(), in.buf.length()); |
---|
3384 | if (!bodyPipe->mayNeedMoreData()) { |
---|
3385 | // BodyPipe will clear us automagically when we produced everything |
---|
3386 | bodyPipe = NULL; |
---|
3387 | } |
---|
3388 | } |
---|
3389 | |
---|
3390 | if (putSize > 0) |
---|
3391 | connNoteUseOfBuffer(this, putSize); |
---|
3392 | |
---|
3393 | if (!bodyPipe) { |
---|
3394 | debugs(33,5, HERE << "produced entire request body for " << clientConnection); |
---|
3395 | |
---|
3396 | if (const char *reason = stoppedSending()) { |
---|
3397 | /* we've finished reading like good clients, |
---|
3398 | * now do the close that initiateClose initiated. |
---|
3399 | */ |
---|
3400 | debugs(33, 3, HERE << "closing for earlier sending error: " << reason); |
---|
3401 | clientConnection->close(); |
---|
3402 | return false; |
---|
3403 | } |
---|
3404 | } |
---|
3405 | |
---|
3406 | return true; |
---|
3407 | } |
---|
3408 | |
---|
3409 | /// parses available chunked encoded body bytes, checks size, returns errors |
---|
3410 | err_type |
---|
3411 | ConnStateData::handleChunkedRequestBody(size_t &putSize) |
---|
3412 | { |
---|
3413 | debugs(33, 7, "chunked from " << clientConnection << ": " << in.buf.length()); |
---|
3414 | |
---|
3415 | try { // the parser will throw on errors |
---|
3416 | |
---|
3417 | if (in.buf.isEmpty()) // nothing to do |
---|
3418 | return ERR_NONE; |
---|
3419 | |
---|
3420 | MemBuf raw; // ChunkedCodingParser only works with MemBufs |
---|
3421 | // add one because MemBuf will assert if it cannot 0-terminate |
---|
3422 | raw.init(in.buf.length(), in.buf.length()+1); |
---|
3423 | raw.append(in.buf.c_str(), in.buf.length()); |
---|
3424 | |
---|
3425 | const mb_size_t wasContentSize = raw.contentSize(); |
---|
3426 | BodyPipeCheckout bpc(*bodyPipe); |
---|
3427 | const bool parsed = in.bodyParser->parse(&raw, &bpc.buf); |
---|
3428 | bpc.checkIn(); |
---|
3429 | putSize = wasContentSize - raw.contentSize(); |
---|
3430 | |
---|
3431 | // dechunk then check: the size limit applies to _dechunked_ content |
---|
3432 | if (clientIsRequestBodyTooLargeForPolicy(bodyPipe->producedSize())) |
---|
3433 | return ERR_TOO_BIG; |
---|
3434 | |
---|
3435 | if (parsed) { |
---|
3436 | finishDechunkingRequest(true); |
---|
3437 | Must(!bodyPipe); |
---|
3438 | return ERR_NONE; // nil bodyPipe implies body end for the caller |
---|
3439 | } |
---|
3440 | |
---|
3441 | // if chunk parser needs data, then the body pipe must need it too |
---|
3442 | Must(!in.bodyParser->needsMoreData() || bodyPipe->mayNeedMoreData()); |
---|
3443 | |
---|
3444 | // if parser needs more space and we can consume nothing, we will stall |
---|
3445 | Must(!in.bodyParser->needsMoreSpace() || bodyPipe->buf().hasContent()); |
---|
3446 | } catch (...) { // TODO: be more specific |
---|
3447 | debugs(33, 3, HERE << "malformed chunks" << bodyPipe->status()); |
---|
3448 | return ERR_INVALID_REQ; |
---|
3449 | } |
---|
3450 | |
---|
3451 | debugs(33, 7, HERE << "need more chunked data" << *bodyPipe->status()); |
---|
3452 | return ERR_NONE; |
---|
3453 | } |
---|
3454 | |
---|
3455 | /// quit on errors related to chunked request body handling |
---|
3456 | void |
---|
3457 | ConnStateData::abortChunkedRequestBody(const err_type error) |
---|
3458 | { |
---|
3459 | finishDechunkingRequest(false); |
---|
3460 | |
---|
3461 | // XXX: The code below works if we fail during initial request parsing, |
---|
3462 | // but if we fail when the server connection is used already, the server may send |
---|
3463 | // us its response too, causing various assertions. How to prevent that? |
---|
3464 | #if WE_KNOW_HOW_TO_SEND_ERRORS |
---|
3465 | ClientSocketContext::Pointer context = getCurrentContext(); |
---|
3466 | if (context != NULL && !context->http->out.offset) { // output nothing yet |
---|
3467 | clientStreamNode *node = context->getClientReplyContext(); |
---|
3468 | clientReplyContext *repContext = dynamic_cast<clientReplyContext*>(node->data.getRaw()); |
---|
3469 | assert(repContext); |
---|
3470 | const Http::StatusCode scode = (error == ERR_TOO_BIG) ? |
---|
3471 | Http::scPayloadTooLarge : HTTP_BAD_REQUEST; |
---|
3472 | repContext->setReplyToError(error, scode, |
---|
3473 | repContext->http->request->method, |
---|
3474 | repContext->http->uri, |
---|
3475 | CachePeer, |
---|
3476 | repContext->http->request, |
---|
3477 | in.buf, NULL); |
---|
3478 | context->pullData(); |
---|
3479 | } else { |
---|
3480 | // close or otherwise we may get stuck as nobody will notice the error? |
---|
3481 | comm_reset_close(clientConnection); |
---|
3482 | } |
---|
3483 | #else |
---|
3484 | debugs(33, 3, HERE << "aborting chunked request without error " << error); |
---|
3485 | comm_reset_close(clientConnection); |
---|
3486 | #endif |
---|
3487 | flags.readMore = false; |
---|
3488 | } |
---|
3489 | |
---|
3490 | void |
---|
3491 | ConnStateData::noteBodyConsumerAborted(BodyPipe::Pointer ) |
---|
3492 | { |
---|
3493 | // request reader may get stuck waiting for space if nobody consumes body |
---|
3494 | if (bodyPipe != NULL) |
---|
3495 | bodyPipe->enableAutoConsumption(); |
---|
3496 | |
---|
3497 | // kids extend |
---|
3498 | } |
---|
3499 | |
---|
3500 | /** general lifetime handler for HTTP requests */ |
---|
3501 | void |
---|
3502 | ConnStateData::requestTimeout(const CommTimeoutCbParams &io) |
---|
3503 | { |
---|
3504 | /* |
---|
3505 | * Just close the connection to not confuse browsers |
---|
3506 | * using persistent connections. Some browsers open |
---|
3507 | * a connection and then do not use it until much |
---|
3508 | * later (presumeably because the request triggering |
---|
3509 | * the open has already been completed on another |
---|
3510 | * connection) |
---|
3511 | */ |
---|
3512 | debugs(33, 3, "requestTimeout: FD " << io.fd << ": lifetime is expired."); |
---|
3513 | io.conn->close(); |
---|
3514 | } |
---|
3515 | |
---|
3516 | static void |
---|
3517 | clientLifetimeTimeout(const CommTimeoutCbParams &io) |
---|
3518 | { |
---|
3519 | ClientHttpRequest *http = static_cast<ClientHttpRequest *>(io.data); |
---|
3520 | debugs(33, DBG_IMPORTANT, "WARNING: Closing client connection due to lifetime timeout"); |
---|
3521 | debugs(33, DBG_IMPORTANT, "\t" << http->uri); |
---|
3522 | http->al->http.timedout = true; |
---|
3523 | if (Comm::IsConnOpen(io.conn)) |
---|
3524 | io.conn->close(); |
---|
3525 | } |
---|
3526 | |
---|
3527 | ConnStateData::ConnStateData(const MasterXaction::Pointer &xact) : |
---|
3528 | AsyncJob("ConnStateData"), // kids overwrite |
---|
3529 | nrequests(0), |
---|
3530 | #if USE_OPENSSL |
---|
3531 | sslBumpMode(Ssl::bumpEnd), |
---|
3532 | #endif |
---|
3533 | needProxyProtocolHeader_(false), |
---|
3534 | #if USE_OPENSSL |
---|
3535 | switchedToHttps_(false), |
---|
3536 | sslServerBump(NULL), |
---|
3537 | signAlgorithm(Ssl::algSignTrusted), |
---|
3538 | #endif |
---|
3539 | stoppedSending_(NULL), |
---|
3540 | stoppedReceiving_(NULL) |
---|
3541 | { |
---|
3542 | flags.readMore = true; // kids may overwrite |
---|
3543 | flags.swanSang = false; |
---|
3544 | |
---|
3545 | pinning.host = NULL; |
---|
3546 | pinning.port = -1; |
---|
3547 | pinning.pinned = false; |
---|
3548 | pinning.auth = false; |
---|
3549 | pinning.zeroReply = false; |
---|
3550 | pinning.peer = NULL; |
---|
3551 | |
---|
3552 | // store the details required for creating more MasterXaction objects as new requests come in |
---|
3553 | clientConnection = xact->tcpClient; |
---|
3554 | port = xact->squidPort; |
---|
3555 | log_addr = xact->tcpClient->remote; |
---|
3556 | log_addr.applyMask(Config.Addrs.client_netmask); |
---|
3557 | |
---|
3558 | // register to receive notice of Squid signal events |
---|
3559 | // which may affect long persisting client connections |
---|
3560 | RegisterRunner(this); |
---|
3561 | } |
---|
3562 | |
---|
3563 | void |
---|
3564 | ConnStateData::start() |
---|
3565 | { |
---|
3566 | BodyProducer::start(); |
---|
3567 | HttpControlMsgSink::start(); |
---|
3568 | |
---|
3569 | if (port->disable_pmtu_discovery != DISABLE_PMTU_OFF && |
---|
3570 | (transparent() || port->disable_pmtu_discovery == DISABLE_PMTU_ALWAYS)) { |
---|
3571 | #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) |
---|
3572 | int i = IP_PMTUDISC_DONT; |
---|
3573 | if (setsockopt(clientConnection->fd, SOL_IP, IP_MTU_DISCOVER, &i, sizeof(i)) < 0) |
---|
3574 | debugs(33, 2, "WARNING: Path MTU discovery disabling failed on " << clientConnection << " : " << xstrerror()); |
---|
3575 | #else |
---|
3576 | static bool reported = false; |
---|
3577 | |
---|
3578 | if (!reported) { |
---|
3579 | debugs(33, DBG_IMPORTANT, "NOTICE: Path MTU discovery disabling is not supported on your platform."); |
---|
3580 | reported = true; |
---|
3581 | } |
---|
3582 | #endif |
---|
3583 | } |
---|
3584 | |
---|
3585 | typedef CommCbMemFunT<ConnStateData, CommCloseCbParams> Dialer; |
---|
3586 | AsyncCall::Pointer call = JobCallback(33, 5, Dialer, this, ConnStateData::connStateClosed); |
---|
3587 | comm_add_close_handler(clientConnection->fd, call); |
---|
3588 | |
---|
3589 | if (Config.onoff.log_fqdn) |
---|
3590 | fqdncache_gethostbyaddr(clientConnection->remote, FQDN_LOOKUP_IF_MISS); |
---|
3591 | |
---|
3592 | #if USE_IDENT |
---|
3593 | if (Ident::TheConfig.identLookup) { |
---|
3594 | ACLFilledChecklist identChecklist(Ident::TheConfig.identLookup, NULL, NULL); |
---|
3595 | identChecklist.src_addr = clientConnection->remote; |
---|
3596 | identChecklist.my_addr = clientConnection->local; |
---|
3597 | if (identChecklist.fastCheck() == ACCESS_ALLOWED) |
---|
3598 | Ident::Start(clientConnection, clientIdentDone, this); |
---|
3599 | } |
---|
3600 | #endif |
---|
3601 | |
---|
3602 | clientdbEstablished(clientConnection->remote, 1); |
---|
3603 | |
---|
3604 | needProxyProtocolHeader_ = port->flags.proxySurrogate; |
---|
3605 | if (needProxyProtocolHeader_) { |
---|
3606 | if (!proxyProtocolValidateClient()) // will close the connection on failure |
---|
3607 | return; |
---|
3608 | } |
---|
3609 | |
---|
3610 | #if USE_DELAY_POOLS |
---|
3611 | fd_table[clientConnection->fd].clientInfo = NULL; |
---|
3612 | |
---|
3613 | if (Config.onoff.client_db) { |
---|
3614 | /* it was said several times that client write limiter does not work if client_db is disabled */ |
---|
3615 | |
---|
3616 | ClientDelayPools& pools(Config.ClientDelay.pools); |
---|
3617 | ACLFilledChecklist ch(NULL, NULL, NULL); |
---|
3618 | |
---|
3619 | // TODO: we check early to limit error response bandwith but we |
---|
3620 | // should recheck when we can honor delay_pool_uses_indirect |
---|
3621 | // TODO: we should also pass the port details for myportname here. |
---|
3622 | ch.src_addr = clientConnection->remote; |
---|
3623 | ch.my_addr = clientConnection->local; |
---|
3624 | |
---|
3625 | for (unsigned int pool = 0; pool < pools.size(); ++pool) { |
---|
3626 | |
---|
3627 | /* pools require explicit 'allow' to assign a client into them */ |
---|
3628 | if (pools[pool].access) { |
---|
3629 | cbdataReferenceDone(ch.accessList); |
---|
3630 | ch.accessList = cbdataReference(pools[pool].access); |
---|
3631 | allow_t answer = ch.fastCheck(); |
---|
3632 | if (answer == ACCESS_ALLOWED) { |
---|
3633 | |
---|
3634 | /* request client information from db after we did all checks |
---|
3635 | this will save hash lookup if client failed checks */ |
---|
3636 | ClientInfo * cli = clientdbGetInfo(clientConnection->remote); |
---|
3637 | assert(cli); |
---|
3638 | |
---|
3639 | /* put client info in FDE */ |
---|
3640 | fd_table[clientConnection->fd].clientInfo = cli; |
---|
3641 | |
---|
3642 | /* setup write limiter for this request */ |
---|
3643 | const double burst = floor(0.5 + |
---|
3644 | (pools[pool].highwatermark * Config.ClientDelay.initial)/100.0); |
---|
3645 | cli->setWriteLimiter(pools[pool].rate, burst, pools[pool].highwatermark); |
---|
3646 | break; |
---|
3647 | } else { |
---|
3648 | debugs(83, 4, HERE << "Delay pool " << pool << " skipped because ACL " << answer); |
---|
3649 | } |
---|
3650 | } |
---|
3651 | } |
---|
3652 | } |
---|
3653 | #endif |
---|
3654 | |
---|
3655 | // kids must extend to actually start doing something (e.g., reading) |
---|
3656 | } |
---|
3657 | |
---|
3658 | /** Handle a new connection on an HTTP socket. */ |
---|
3659 | void |
---|
3660 | httpAccept(const CommAcceptCbParams ¶ms) |
---|
3661 | { |
---|
3662 | MasterXaction::Pointer xact = params.xaction; |
---|
3663 | AnyP::PortCfgPointer s = xact->squidPort; |
---|
3664 | |
---|
3665 | // NP: it is possible the port was reconfigured when the call or accept() was queued. |
---|
3666 | |
---|
3667 | if (params.flag != Comm::OK) { |
---|
3668 | // Its possible the call was still queued when the client disconnected |
---|
3669 | debugs(33, 2, s->listenConn << ": accept failure: " << xstrerr(params.xerrno)); |
---|
3670 | return; |
---|
3671 | } |
---|
3672 | |
---|
3673 | debugs(33, 4, params.conn << ": accepted"); |
---|
3674 | fd_note(params.conn->fd, "client http connect"); |
---|
3675 | |
---|
3676 | if (s->tcp_keepalive.enabled) |
---|
3677 | commSetTcpKeepalive(params.conn->fd, s->tcp_keepalive.idle, s->tcp_keepalive.interval, s->tcp_keepalive.timeout); |
---|
3678 | |
---|
3679 | ++incoming_sockets_accepted; |
---|
3680 | |
---|
3681 | // Socket is ready, setup the connection manager to start using it |
---|
3682 | ConnStateData *connState = Http::NewServer(xact); |
---|
3683 | AsyncJob::Start(connState); // usually async-calls readSomeData() |
---|
3684 | } |
---|
3685 | |
---|
3686 | #if USE_OPENSSL |
---|
3687 | |
---|
3688 | /** Create SSL connection structure and update fd_table */ |
---|
3689 | static SSL * |
---|
3690 | httpsCreate(const Comm::ConnectionPointer &conn, SSL_CTX *sslContext) |
---|
3691 | { |
---|
3692 | if (SSL *ssl = Ssl::CreateServer(sslContext, conn->fd, "client https start")) { |
---|
3693 | debugs(33, 5, "will negotate SSL on " << conn); |
---|
3694 | return ssl; |
---|
3695 | } |
---|
3696 | |
---|
3697 | conn->close(); |
---|
3698 | return NULL; |
---|
3699 | } |
---|
3700 | |
---|
3701 | static bool |
---|
3702 | Squid_SSL_accept(ConnStateData *conn, PF *callback) |
---|
3703 | { |
---|
3704 | int fd = conn->clientConnection->fd; |
---|
3705 | SSL *ssl = fd_table[fd].ssl; |
---|
3706 | int ret; |
---|
3707 | |
---|
3708 | errno = 0; |
---|
3709 | if ((ret = SSL_accept(ssl)) <= 0) { |
---|
3710 | int xerrno = errno; |
---|
3711 | int ssl_error = SSL_get_error(ssl, ret); |
---|
3712 | |
---|
3713 | switch (ssl_error) { |
---|
3714 | |
---|
3715 | case SSL_ERROR_WANT_READ: |
---|
3716 | Comm::SetSelect(fd, COMM_SELECT_READ, callback, conn, 0); |
---|
3717 | return false; |
---|
3718 | |
---|
3719 | case SSL_ERROR_WANT_WRITE: |
---|
3720 | Comm::SetSelect(fd, COMM_SELECT_WRITE, callback, conn, 0); |
---|
3721 | return false; |
---|
3722 | |
---|
3723 | case SSL_ERROR_SYSCALL: |
---|
3724 | if (ret == 0) { |
---|
3725 | debugs(83, 2, "Error negotiating SSL connection on FD " << fd << ": Aborted by client: " << ssl_error); |
---|
3726 | } else { |
---|
3727 | debugs(83, (xerrno == ECONNRESET) ? 1 : 2, "Error negotiating SSL connection on FD " << fd << ": " << |
---|
3728 | (xerrno == 0 ? ERR_error_string(ssl_error, NULL) : xstrerr(xerrno))); |
---|
3729 | } |
---|
3730 | conn->clientConnection->close(); |
---|
3731 | return false; |
---|
3732 | |
---|
3733 | case SSL_ERROR_ZERO_RETURN: |
---|
3734 | debugs(83, DBG_IMPORTANT, "Error negotiating SSL connection on FD " << fd << ": Closed by client"); |
---|
3735 | conn->clientConnection->close(); |
---|
3736 | return false; |
---|
3737 | |
---|
3738 | default: |
---|
3739 | debugs(83, DBG_IMPORTANT, "Error negotiating SSL connection on FD " << |
---|
3740 | fd << ": " << ERR_error_string(ERR_get_error(), NULL) << |
---|
3741 | " (" << ssl_error << "/" << ret << ")"); |
---|
3742 | conn->clientConnection->close(); |
---|
3743 | return false; |
---|
3744 | } |
---|
3745 | |
---|
3746 | /* NOTREACHED */ |
---|
3747 | } |
---|
3748 | return true; |
---|
3749 | } |
---|
3750 | |
---|
3751 | /** negotiate an SSL connection */ |
---|
3752 | static void |
---|
3753 | clientNegotiateSSL(int fd, void *data) |
---|
3754 | { |
---|
3755 | ConnStateData *conn = (ConnStateData *)data; |
---|
3756 | X509 *client_cert; |
---|
3757 | SSL *ssl = fd_table[fd].ssl; |
---|
3758 | |
---|
3759 | if (!Squid_SSL_accept(conn, clientNegotiateSSL)) |
---|
3760 | return; |
---|
3761 | |
---|
3762 | if (SSL_session_reused(ssl)) { |
---|
3763 | debugs(83, 2, "clientNegotiateSSL: Session " << SSL_get_session(ssl) << |
---|
3764 | " reused on FD " << fd << " (" << fd_table[fd].ipaddr << ":" << (int)fd_table[fd].remote_port << ")"); |
---|
3765 | } else { |
---|
3766 | if (do_debug(83, 4)) { |
---|
3767 | /* Write out the SSL session details.. actually the call below, but |
---|
3768 | * OpenSSL headers do strange typecasts confusing GCC.. */ |
---|
3769 | /* PEM_write_SSL_SESSION(debug_log, SSL_get_session(ssl)); */ |
---|
3770 | #if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x00908000L |
---|
3771 | PEM_ASN1_write((i2d_of_void *)i2d_SSL_SESSION, PEM_STRING_SSL_SESSION, debug_log, (char *)SSL_get_session(ssl), NULL,NULL,0,NULL,NULL); |
---|
3772 | |
---|
3773 | #elif (ALLOW_ALWAYS_SSL_SESSION_DETAIL == 1) |
---|
3774 | |
---|
3775 | /* When using gcc 3.3.x and OpenSSL 0.9.7x sometimes a compile error can occur here. |
---|
3776 | * This is caused by an unpredicatble gcc behaviour on a cast of the first argument |
---|
3777 | * of PEM_ASN1_write(). For this reason this code section is disabled. To enable it, |
---|
3778 | * define ALLOW_ALWAYS_SSL_SESSION_DETAIL=1. |
---|
3779 | * Because there are two possible usable cast, if you get an error here, try the other |
---|
3780 | * commented line. */ |
---|
3781 | |
---|
3782 | PEM_ASN1_write((int(*)())i2d_SSL_SESSION, PEM_STRING_SSL_SESSION, debug_log, (char *)SSL_get_session(ssl), NULL,NULL,0,NULL,NULL); |
---|
3783 | /* PEM_ASN1_write((int(*)(...))i2d_SSL_SESSION, PEM_STRING_SSL_SESSION, debug_log, (char *)SSL_get_session(ssl), NULL,NULL,0,NULL,NULL); */ |
---|
3784 | |
---|
3785 | #else |
---|
3786 | |
---|
3787 | debugs(83, 4, "With " OPENSSL_VERSION_TEXT ", session details are available only defining ALLOW_ALWAYS_SSL_SESSION_DETAIL=1 in the source." ); |
---|
3788 | |
---|
3789 | #endif |
---|
3790 | /* Note: This does not automatically fflush the log file.. */ |
---|
3791 | } |
---|
3792 | |
---|
3793 | debugs(83, 2, "clientNegotiateSSL: New session " << |
---|
3794 | SSL_get_session(ssl) << " on FD " << fd << " (" << |
---|
3795 | fd_table[fd].ipaddr << ":" << (int)fd_table[fd].remote_port << |
---|
3796 | ")"); |
---|
3797 | } |
---|
3798 | |
---|
3799 | debugs(83, 3, "clientNegotiateSSL: FD " << fd << " negotiated cipher " << |
---|
3800 | SSL_get_cipher(ssl)); |
---|
3801 | |
---|
3802 | client_cert = SSL_get_peer_certificate(ssl); |
---|
3803 | |
---|
3804 | if (client_cert != NULL) { |
---|
3805 | debugs(83, 3, "clientNegotiateSSL: FD " << fd << |
---|
3806 | " client certificate: subject: " << |
---|
3807 | X509_NAME_oneline(X509_get_subject_name(client_cert), 0, 0)); |
---|
3808 | |
---|
3809 | debugs(83, 3, "clientNegotiateSSL: FD " << fd << |
---|
3810 | " client certificate: issuer: " << |
---|
3811 | X509_NAME_oneline(X509_get_issuer_name(client_cert), 0, 0)); |
---|
3812 | |
---|
3813 | X509_free(client_cert); |
---|
3814 | } else { |
---|
3815 | debugs(83, 5, "clientNegotiateSSL: FD " << fd << |
---|
3816 | " has no certificate."); |
---|
3817 | } |
---|
3818 | |
---|
3819 | #if defined(TLSEXT_NAMETYPE_host_name) |
---|
3820 | if (!conn->serverBump()) { |
---|
3821 | // when in bumpClientFirst mode, get the server name from SNI |
---|
3822 | if (const char *server = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name)) |
---|
3823 | conn->resetSslCommonName(server); |
---|
3824 | } |
---|
3825 | #endif |
---|
3826 | |
---|
3827 | conn->readSomeData(); |
---|
3828 | } |
---|
3829 | |
---|
3830 | /** |
---|
3831 | * If SSL_CTX is given, starts reading the SSL handshake. |
---|
3832 | * Otherwise, calls switchToHttps to generate a dynamic SSL_CTX. |
---|
3833 | */ |
---|
3834 | static void |
---|
3835 | httpsEstablish(ConnStateData *connState, SSL_CTX *sslContext, Ssl::BumpMode bumpMode) |
---|
3836 | { |
---|
3837 | SSL *ssl = NULL; |
---|
3838 | assert(connState); |
---|
3839 | const Comm::ConnectionPointer &details = connState->clientConnection; |
---|
3840 | |
---|
3841 | if (sslContext && !(ssl = httpsCreate(details, sslContext))) |
---|
3842 | return; |
---|
3843 | |
---|
3844 | typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer; |
---|
3845 | AsyncCall::Pointer timeoutCall = JobCallback(33, 5, TimeoutDialer, |
---|
3846 | connState, ConnStateData::requestTimeout); |
---|
3847 | commSetConnTimeout(details, Config.Timeout.request, timeoutCall); |
---|
3848 | |
---|
3849 | if (ssl) |
---|
3850 | Comm::SetSelect(details->fd, COMM_SELECT_READ, clientNegotiateSSL, connState, 0); |
---|
3851 | else { |
---|
3852 | char buf[MAX_IPSTRLEN]; |
---|
3853 | assert(bumpMode != Ssl::bumpNone && bumpMode != Ssl::bumpEnd); |
---|
3854 | HttpRequest::Pointer fakeRequest(new HttpRequest); |
---|
3855 | fakeRequest->SetHost(details->local.toStr(buf, sizeof(buf))); |
---|
3856 | fakeRequest->port = details->local.port(); |
---|
3857 | fakeRequest->clientConnectionManager = connState; |
---|
3858 | fakeRequest->client_addr = connState->clientConnection->remote; |
---|
3859 | #if FOLLOW_X_FORWARDED_FOR |
---|
3860 | fakeRequest->indirect_client_addr = connState->clientConnection->remote; |
---|
3861 | #endif |
---|
3862 | fakeRequest->my_addr = connState->clientConnection->local; |
---|
3863 | fakeRequest->flags.interceptTproxy = ((connState->clientConnection->flags & COMM_TRANSPARENT) != 0 ) ; |
---|
3864 | fakeRequest->flags.intercepted = ((connState->clientConnection->flags & COMM_INTERCEPTION) != 0); |
---|
3865 | fakeRequest->myportname = connState->port->name; |
---|
3866 | if (fakeRequest->flags.interceptTproxy) { |
---|
3867 | if (Config.accessList.spoof_client_ip) { |
---|
3868 | ACLFilledChecklist checklist(Config.accessList.spoof_client_ip, fakeRequest.getRaw(), NULL); |
---|
3869 | fakeRequest->flags.spoofClientIp = (checklist.fastCheck() == ACCESS_ALLOWED); |
---|
3870 | } else |
---|
3871 | fakeRequest->flags.spoofClientIp = true; |
---|
3872 | } else |
---|
3873 | fakeRequest->flags.spoofClientIp = false; |
---|
3874 | debugs(33, 4, HERE << details << " try to generate a Dynamic SSL CTX"); |
---|
3875 | connState->switchToHttps(fakeRequest.getRaw(), bumpMode); |
---|
3876 | } |
---|
3877 | } |
---|
3878 | |
---|
3879 | /** |
---|
3880 | * A callback function to use with the ACLFilledChecklist callback. |
---|
3881 | * In the case of ACCESS_ALLOWED answer initializes a bumped SSL connection, |
---|
3882 | * else reverts the connection to tunnel mode. |
---|
3883 | */ |
---|
3884 | static void |
---|
3885 | httpsSslBumpAccessCheckDone(allow_t answer, void *data) |
---|
3886 | { |
---|
3887 | ConnStateData *connState = (ConnStateData *) data; |
---|
3888 | |
---|
3889 | // if the connection is closed or closing, just return. |
---|
3890 | if (!connState->isOpen()) |
---|
3891 | return; |
---|
3892 | |
---|
3893 | // Require both a match and a positive bump mode to work around exceptional |
---|
3894 | // cases where ACL code may return ACCESS_ALLOWED with zero answer.kind. |
---|
3895 | if (answer == ACCESS_ALLOWED && (answer.kind != Ssl::bumpNone && answer.kind != Ssl::bumpSplice)) { |
---|
3896 | debugs(33, 2, "sslBump needed for " << connState->clientConnection << " method " << answer.kind); |
---|
3897 | connState->sslBumpMode = static_cast<Ssl::BumpMode>(answer.kind); |
---|
3898 | } else { |
---|
3899 | debugs(33, 2, HERE << "sslBump not needed for " << connState->clientConnection); |
---|
3900 | connState->sslBumpMode = Ssl::bumpNone; |
---|
3901 | } |
---|
3902 | connState->fakeAConnectRequest("ssl-bump", connState->in.buf); |
---|
3903 | } |
---|
3904 | |
---|
3905 | /** handle a new HTTPS connection */ |
---|
3906 | static void |
---|
3907 | httpsAccept(const CommAcceptCbParams ¶ms) |
---|
3908 | { |
---|
3909 | MasterXaction::Pointer xact = params.xaction; |
---|
3910 | const AnyP::PortCfgPointer s = xact->squidPort; |
---|
3911 | |
---|
3912 | // NP: it is possible the port was reconfigured when the call or accept() was queued. |
---|
3913 | |
---|
3914 | if (params.flag != Comm::OK) { |
---|
3915 | // Its possible the call was still queued when the client disconnected |
---|
3916 | debugs(33, 2, "httpsAccept: " << s->listenConn << ": accept failure: " << xstrerr(params.xerrno)); |
---|
3917 | return; |
---|
3918 | } |
---|
3919 | |
---|
3920 | debugs(33, 4, HERE << params.conn << " accepted, starting SSL negotiation."); |
---|
3921 | fd_note(params.conn->fd, "client https connect"); |
---|
3922 | |
---|
3923 | if (s->tcp_keepalive.enabled) { |
---|
3924 | commSetTcpKeepalive(params.conn->fd, s->tcp_keepalive.idle, s->tcp_keepalive.interval, s->tcp_keepalive.timeout); |
---|
3925 | } |
---|
3926 | |
---|
3927 | ++incoming_sockets_accepted; |
---|
3928 | |
---|
3929 | // Socket is ready, setup the connection manager to start using it |
---|
3930 | ConnStateData *connState = Https::NewServer(xact); |
---|
3931 | AsyncJob::Start(connState); // usually async-calls postHttpsAccept() |
---|
3932 | } |
---|
3933 | |
---|
3934 | void |
---|
3935 | ConnStateData::postHttpsAccept() |
---|
3936 | { |
---|
3937 | if (port->flags.tunnelSslBumping) { |
---|
3938 | debugs(33, 5, "accept transparent connection: " << clientConnection); |
---|
3939 | |
---|
3940 | if (!Config.accessList.ssl_bump) { |
---|
3941 | httpsSslBumpAccessCheckDone(ACCESS_DENIED, this); |
---|
3942 | return; |
---|
3943 | } |
---|
3944 | |
---|
3945 | // Create a fake HTTP request for ssl_bump ACL check, |
---|
3946 | // using tproxy/intercept provided destination IP and port. |
---|
3947 | HttpRequest *request = new HttpRequest(); |
---|
3948 | static char ip[MAX_IPSTRLEN]; |
---|
3949 | assert(clientConnection->flags & (COMM_TRANSPARENT | COMM_INTERCEPTION)); |
---|
3950 | request->SetHost(clientConnection->local.toStr(ip, sizeof(ip))); |
---|
3951 | request->port = clientConnection->local.port(); |
---|
3952 | request->myportname = port->name; |
---|
3953 | |
---|
3954 | ACLFilledChecklist *acl_checklist = new ACLFilledChecklist(Config.accessList.ssl_bump, request, NULL); |
---|
3955 | acl_checklist->src_addr = clientConnection->remote; |
---|
3956 | acl_checklist->my_addr = port->s; |
---|
3957 | acl_checklist->nonBlockingCheck(httpsSslBumpAccessCheckDone, this); |
---|
3958 | return; |
---|
3959 | } else { |
---|
3960 | SSL_CTX *sslContext = port->staticSslContext.get(); |
---|
3961 | httpsEstablish(this, sslContext, Ssl::bumpNone); |
---|
3962 | } |
---|
3963 | } |
---|
3964 | |
---|
3965 | void |
---|
3966 | ConnStateData::sslCrtdHandleReplyWrapper(void *data, const Helper::Reply &reply) |
---|
3967 | { |
---|
3968 | ConnStateData * state_data = (ConnStateData *)(data); |
---|
3969 | state_data->sslCrtdHandleReply(reply); |
---|
3970 | } |
---|
3971 | |
---|
3972 | void |
---|
3973 | ConnStateData::sslCrtdHandleReply(const Helper::Reply &reply) |
---|
3974 | { |
---|
3975 | if (!isOpen()) { |
---|
3976 | debugs(33, 3, "Connection gone while waiting for ssl_crtd helper reply; helper reply:" << reply); |
---|
3977 | return; |
---|
3978 | } |
---|
3979 | |
---|
3980 | if (reply.result == Helper::BrokenHelper) { |
---|
3981 | debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " cannot be generated. ssl_crtd response: " << reply); |
---|
3982 | } else if (!reply.other().hasContent()) { |
---|
3983 | debugs(1, DBG_IMPORTANT, HERE << "\"ssl_crtd\" helper returned <NULL> reply."); |
---|
3984 | } else { |
---|
3985 | Ssl::CrtdMessage reply_message(Ssl::CrtdMessage::REPLY); |
---|
3986 | if (reply_message.parse(reply.other().content(), reply.other().contentSize()) != Ssl::CrtdMessage::OK) { |
---|
3987 | debugs(33, 5, HERE << "Reply from ssl_crtd for " << sslConnectHostOrIp << " is incorrect"); |
---|
3988 | } else { |
---|
3989 | if (reply.result != Helper::Okay) { |
---|
3990 | debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " cannot be generated. ssl_crtd response: " << reply_message.getBody()); |
---|
3991 | } else { |
---|
3992 | debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " was successfully recieved from ssl_crtd"); |
---|
3993 | if (sslServerBump && (sslServerBump->act.step1 == Ssl::bumpPeek || sslServerBump->act.step1 == Ssl::bumpStare)) { |
---|
3994 | doPeekAndSpliceStep(); |
---|
3995 | SSL *ssl = fd_table[clientConnection->fd].ssl; |
---|
3996 | bool ret = Ssl::configureSSLUsingPkeyAndCertFromMemory(ssl, reply_message.getBody().c_str(), *port); |
---|
3997 | if (!ret) |
---|
3998 | debugs(33, 5, "Failed to set certificates to ssl object for PeekAndSplice mode"); |
---|
3999 | } else { |
---|
4000 | SSL_CTX *ctx = Ssl::generateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str(), *port); |
---|
4001 | getSslContextDone(ctx, true); |
---|
4002 | } |
---|
4003 | return; |
---|
4004 | } |
---|
4005 | } |
---|
4006 | } |
---|
4007 | getSslContextDone(NULL); |
---|
4008 | } |
---|
4009 | |
---|
4010 | void ConnStateData::buildSslCertGenerationParams(Ssl::CertificateProperties &certProperties) |
---|
4011 | { |
---|
4012 | certProperties.commonName = sslCommonName_.isEmpty() ? sslConnectHostOrIp.termedBuf() : sslCommonName_.c_str(); |
---|
4013 | |
---|
4014 | // fake certificate adaptation requires bump-server-first mode |
---|
4015 | if (!sslServerBump) { |
---|
4016 | assert(port->signingCert.get()); |
---|
4017 | certProperties.signWithX509.resetAndLock(port->signingCert.get()); |
---|
4018 | if (port->signPkey.get()) |
---|
4019 | certProperties.signWithPkey.resetAndLock(port->signPkey.get()); |
---|
4020 | certProperties.signAlgorithm = Ssl::algSignTrusted; |
---|
4021 | return; |
---|
4022 | } |
---|
4023 | |
---|
4024 | // In case of an error while connecting to the secure server, use a fake |
---|
4025 | // trusted certificate, with no mimicked fields and no adaptation |
---|
4026 | // algorithms. There is nothing we can mimic so we want to minimize the |
---|
4027 | // number of warnings the user will have to see to get to the error page. |
---|
4028 | assert(sslServerBump->entry); |
---|
4029 | if (sslServerBump->entry->isEmpty()) { |
---|
4030 | if (X509 *mimicCert = sslServerBump->serverCert.get()) |
---|
4031 | certProperties.mimicCert.resetAndLock(mimicCert); |
---|
4032 | |
---|
4033 | ACLFilledChecklist checklist(NULL, sslServerBump->request.getRaw(), |
---|
4034 | clientConnection != NULL ? clientConnection->rfc931 : dash_str); |
---|
4035 | checklist.sslErrors = cbdataReference(sslServerBump->sslErrors); |
---|
4036 | |
---|
4037 | for (sslproxy_cert_adapt *ca = Config.ssl_client.cert_adapt; ca != NULL; ca = ca->next) { |
---|
4038 | // If the algorithm already set, then ignore it. |
---|
4039 | if ((ca->alg == Ssl::algSetCommonName && certProperties.setCommonName) || |
---|
4040 | (ca->alg == Ssl::algSetValidAfter && certProperties.setValidAfter) || |
---|
4041 | (ca->alg == Ssl::algSetValidBefore && certProperties.setValidBefore) ) |
---|
4042 | continue; |
---|
4043 | |
---|
4044 | if (ca->aclList && checklist.fastCheck(ca->aclList) == ACCESS_ALLOWED) { |
---|
4045 | const char *alg = Ssl::CertAdaptAlgorithmStr[ca->alg]; |
---|
4046 | const char *param = ca->param; |
---|
4047 | |
---|
4048 | // For parameterless CN adaptation, use hostname from the |
---|
4049 | // CONNECT request. |
---|
4050 | if (ca->alg == Ssl::algSetCommonName) { |
---|
4051 | if (!param) |
---|
4052 | param = sslConnectHostOrIp.termedBuf(); |
---|
4053 | certProperties.commonName = param; |
---|
4054 | certProperties.setCommonName = true; |
---|
4055 | } else if (ca->alg == Ssl::algSetValidAfter) |
---|
4056 | certProperties.setValidAfter = true; |
---|
4057 | else if (ca->alg == Ssl::algSetValidBefore) |
---|
4058 | certProperties.setValidBefore = true; |
---|
4059 | |
---|
4060 | debugs(33, 5, HERE << "Matches certificate adaptation aglorithm: " << |
---|
4061 | alg << " param: " << (param ? param : "-")); |
---|
4062 | } |
---|
4063 | } |
---|
4064 | |
---|
4065 | certProperties.signAlgorithm = Ssl::algSignEnd; |
---|
4066 | for (sslproxy_cert_sign *sg = Config.ssl_client.cert_sign; sg != NULL; sg = sg->next) { |
---|
4067 | if (sg->aclList && checklist.fastCheck(sg->aclList) == ACCESS_ALLOWED) { |
---|
4068 | certProperties.signAlgorithm = (Ssl::CertSignAlgorithm)sg->alg; |
---|
4069 | break; |
---|
4070 | } |
---|
4071 | } |
---|
4072 | } else {// if (!sslServerBump->entry->isEmpty()) |
---|
4073 | // Use trusted certificate for a Squid-generated error |
---|
4074 | // or the user would have to add a security exception |
---|
4075 | // just to see the error page. We will close the connection |
---|
4076 | // so that the trust is not extended to non-Squid content. |
---|
4077 | certProperties.signAlgorithm = Ssl::algSignTrusted; |
---|
4078 | } |
---|
4079 | |
---|
4080 | assert(certProperties.signAlgorithm != Ssl::algSignEnd); |
---|
4081 | |
---|
4082 | if (certProperties.signAlgorithm == Ssl::algSignUntrusted) { |
---|
4083 | assert(port->untrustedSigningCert.get()); |
---|
4084 | certProperties.signWithX509.resetAndLock(port->untrustedSigningCert.get()); |
---|
4085 | certProperties.signWithPkey.resetAndLock(port->untrustedSignPkey.get()); |
---|
4086 | } else { |
---|
4087 | assert(port->signingCert.get()); |
---|
4088 | certProperties.signWithX509.resetAndLock(port->signingCert.get()); |
---|
4089 | |
---|
4090 | if (port->signPkey.get()) |
---|
4091 | certProperties.signWithPkey.resetAndLock(port->signPkey.get()); |
---|
4092 | } |
---|
4093 | signAlgorithm = certProperties.signAlgorithm; |
---|
4094 | |
---|
4095 | certProperties.signHash = Ssl::DefaultSignHash; |
---|
4096 | } |
---|
4097 | |
---|
4098 | void |
---|
4099 | ConnStateData::getSslContextStart() |
---|
4100 | { |
---|
4101 | assert(areAllContextsForThisConnection()); |
---|
4102 | freeAllContexts(); |
---|
4103 | /* careful: freeAllContexts() above frees request, host, etc. */ |
---|
4104 | |
---|
4105 | if (port->generateHostCertificates) { |
---|
4106 | Ssl::CertificateProperties certProperties; |
---|
4107 | buildSslCertGenerationParams(certProperties); |
---|
4108 | sslBumpCertKey = certProperties.dbKey().c_str(); |
---|
4109 | assert(sslBumpCertKey.size() > 0 && sslBumpCertKey[0] != '\0'); |
---|
4110 | |
---|
4111 | // Disable caching for bumpPeekAndSplice mode |
---|
4112 | if (!(sslServerBump && (sslServerBump->act.step1 == Ssl::bumpPeek || sslServerBump->act.step1 == Ssl::bumpStare))) { |
---|
4113 | debugs(33, 5, "Finding SSL certificate for " << sslBumpCertKey << " in cache"); |
---|
4114 | Ssl::LocalContextStorage * ssl_ctx_cache = Ssl::TheGlobalContextStorage.getLocalStorage(port->s); |
---|
4115 | SSL_CTX * dynCtx = NULL; |
---|
4116 | Ssl::SSL_CTX_Pointer *cachedCtx = ssl_ctx_cache ? ssl_ctx_cache->get(sslBumpCertKey.termedBuf()) : NULL; |
---|
4117 | if (cachedCtx && (dynCtx = cachedCtx->get())) { |
---|
4118 | debugs(33, 5, "SSL certificate for " << sslBumpCertKey << " found in cache"); |
---|
4119 | if (Ssl::verifySslCertificate(dynCtx, certProperties)) { |
---|
4120 | debugs(33, 5, "Cached SSL certificate for " << sslBumpCertKey << " is valid"); |
---|
4121 | getSslContextDone(dynCtx); |
---|
4122 | return; |
---|
4123 | } else { |
---|
4124 | debugs(33, 5, "Cached SSL certificate for " << sslBumpCertKey << " is out of date. Delete this certificate from cache"); |
---|
4125 | if (ssl_ctx_cache) |
---|
4126 | ssl_ctx_cache->del(sslBumpCertKey.termedBuf()); |
---|
4127 | } |
---|
4128 | } else { |
---|
4129 | debugs(33, 5, "SSL certificate for " << sslBumpCertKey << " haven't found in cache"); |
---|
4130 | } |
---|
4131 | } |
---|
4132 | |
---|
4133 | #if USE_SSL_CRTD |
---|
4134 | try { |
---|
4135 | debugs(33, 5, HERE << "Generating SSL certificate for " << certProperties.commonName << " using ssl_crtd."); |
---|
4136 | Ssl::CrtdMessage request_message(Ssl::CrtdMessage::REQUEST); |
---|
4137 | request_message.setCode(Ssl::CrtdMessage::code_new_certificate); |
---|
4138 | request_message.composeRequest(certProperties); |
---|
4139 | debugs(33, 5, HERE << "SSL crtd request: " << request_message.compose().c_str()); |
---|
4140 | Ssl::Helper::GetInstance()->sslSubmit(request_message, sslCrtdHandleReplyWrapper, this); |
---|
4141 | return; |
---|
4142 | } catch (const std::exception &e) { |
---|
4143 | debugs(33, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtd " << |
---|
4144 | "request for " << certProperties.commonName << |
---|
4145 | " certificate: " << e.what() << "; will now block to " << |
---|
4146 | "generate that certificate."); |
---|
4147 | // fall through to do blocking in-process generation. |
---|
4148 | } |
---|
4149 | #endif // USE_SSL_CRTD |
---|
4150 | |
---|
4151 | debugs(33, 5, HERE << "Generating SSL certificate for " << certProperties.commonName); |
---|
4152 | if (sslServerBump && (sslServerBump->act.step1 == Ssl::bumpPeek || sslServerBump->act.step1 == Ssl::bumpStare)) { |
---|
4153 | doPeekAndSpliceStep(); |
---|
4154 | SSL *ssl = fd_table[clientConnection->fd].ssl; |
---|
4155 | if (!Ssl::configureSSL(ssl, certProperties, *port)) |
---|
4156 | debugs(33, 5, "Failed to set certificates to ssl object for PeekAndSplice mode"); |
---|
4157 | } else { |
---|
4158 | SSL_CTX *dynCtx = Ssl::generateSslContext(certProperties, *port); |
---|
4159 | getSslContextDone(dynCtx, true); |
---|
4160 | } |
---|
4161 | return; |
---|
4162 | } |
---|
4163 | getSslContextDone(NULL); |
---|
4164 | } |
---|
4165 | |
---|
4166 | void |
---|
4167 | ConnStateData::getSslContextDone(SSL_CTX * sslContext, bool isNew) |
---|
4168 | { |
---|
4169 | // Try to add generated ssl context to storage. |
---|
4170 | if (port->generateHostCertificates && isNew) { |
---|
4171 | |
---|
4172 | if (signAlgorithm == Ssl::algSignTrusted) { |
---|
4173 | // Add signing certificate to the certificates chain |
---|
4174 | X509 *cert = port->signingCert.get(); |
---|
4175 | if (SSL_CTX_add_extra_chain_cert(sslContext, cert)) { |
---|
4176 | // increase the certificate lock |
---|
4177 | CRYPTO_add(&(cert->references),1,CRYPTO_LOCK_X509); |
---|
4178 | } else { |
---|
4179 | const int ssl_error = ERR_get_error(); |
---|
4180 | debugs(33, DBG_IMPORTANT, "WARNING: can not add signing certificate to SSL context chain: " << ERR_error_string(ssl_error, NULL)); |
---|
4181 | } |
---|
4182 | Ssl::addChainToSslContext(sslContext, port->certsToChain.get()); |
---|
4183 | } |
---|
4184 | //else it is self-signed or untrusted do not attrach any certificate |
---|
4185 | |
---|
4186 | Ssl::LocalContextStorage *ssl_ctx_cache = Ssl::TheGlobalContextStorage.getLocalStorage(port->s); |
---|
4187 | assert(sslBumpCertKey.size() > 0 && sslBumpCertKey[0] != '\0'); |
---|
4188 | if (sslContext) { |
---|
4189 | if (!ssl_ctx_cache || !ssl_ctx_cache->add(sslBumpCertKey.termedBuf(), new Ssl::SSL_CTX_Pointer(sslContext))) { |
---|
4190 | // If it is not in storage delete after using. Else storage deleted it. |
---|
4191 | fd_table[clientConnection->fd].dynamicSslContext = sslContext; |
---|
4192 | } |
---|
4193 | } else { |
---|
4194 | debugs(33, 2, HERE << "Failed to generate SSL cert for " << sslConnectHostOrIp); |
---|
4195 | } |
---|
4196 | } |
---|
4197 | |
---|
4198 | // If generated ssl context = NULL, try to use static ssl context. |
---|
4199 | if (!sslContext) { |
---|
4200 | if (!port->staticSslContext) { |
---|
4201 | debugs(83, DBG_IMPORTANT, "Closing SSL " << clientConnection->remote << " as lacking SSL context"); |
---|
4202 | clientConnection->close(); |
---|
4203 | return; |
---|
4204 | } else { |
---|
4205 | debugs(33, 5, HERE << "Using static ssl context."); |
---|
4206 | sslContext = port->staticSslContext.get(); |
---|
4207 | } |
---|
4208 | } |
---|
4209 | |
---|
4210 | if (!httpsCreate(clientConnection, sslContext)) |
---|
4211 | return; |
---|
4212 | |
---|
4213 | // bumped intercepted conns should already have Config.Timeout.request set |
---|
4214 | // but forwarded connections may only have Config.Timeout.lifetime. [Re]set |
---|
4215 | // to make sure the connection does not get stuck on non-SSL clients. |
---|
4216 | typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer; |
---|
4217 | AsyncCall::Pointer timeoutCall = JobCallback(33, 5, TimeoutDialer, |
---|
4218 | this, ConnStateData::requestTimeout); |
---|
4219 | commSetConnTimeout(clientConnection, Config.Timeout.request, timeoutCall); |
---|
4220 | |
---|
4221 | // Disable the client read handler until CachePeer selection is complete |
---|
4222 | Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0); |
---|
4223 | Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, clientNegotiateSSL, this, 0); |
---|
4224 | switchedToHttps_ = true; |
---|
4225 | } |
---|
4226 | |
---|
4227 | void |
---|
4228 | ConnStateData::switchToHttps(HttpRequest *request, Ssl::BumpMode bumpServerMode) |
---|
4229 | { |
---|
4230 | assert(!switchedToHttps_); |
---|
4231 | |
---|
4232 | sslConnectHostOrIp = request->GetHost(); |
---|
4233 | resetSslCommonName(request->GetHost()); |
---|
4234 | |
---|
4235 | // We are going to read new request |
---|
4236 | flags.readMore = true; |
---|
4237 | debugs(33, 5, HERE << "converting " << clientConnection << " to SSL"); |
---|
4238 | |
---|
4239 | // If sslServerBump is set, then we have decided to deny CONNECT |
---|
4240 | // and now want to switch to SSL to send the error to the client |
---|
4241 | // without even peeking at the origin server certificate. |
---|
4242 | if (bumpServerMode == Ssl::bumpServerFirst && !sslServerBump) { |
---|
4243 | request->flags.sslPeek = true; |
---|
4244 | sslServerBump = new Ssl::ServerBump(request); |
---|
4245 | |
---|
4246 | // will call httpsPeeked() with certificate and connection, eventually |
---|
4247 | FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw()); |
---|
4248 | return; |
---|
4249 | } else if (bumpServerMode == Ssl::bumpPeek || bumpServerMode == Ssl::bumpStare) { |
---|
4250 | request->flags.sslPeek = true; |
---|
4251 | sslServerBump = new Ssl::ServerBump(request, NULL, bumpServerMode); |
---|
4252 | startPeekAndSplice(); |
---|
4253 | return; |
---|
4254 | } |
---|
4255 | |
---|
4256 | // otherwise, use sslConnectHostOrIp |
---|
4257 | getSslContextStart(); |
---|
4258 | } |
---|
4259 | |
---|
4260 | /** negotiate an SSL connection */ |
---|
4261 | static void |
---|
4262 | clientPeekAndSpliceSSL(int fd, void *data) |
---|
4263 | { |
---|
4264 | ConnStateData *conn = (ConnStateData *)data; |
---|
4265 | SSL *ssl = fd_table[fd].ssl; |
---|
4266 | |
---|
4267 | debugs(83, 5, "Start peek and splice on FD " << fd); |
---|
4268 | |
---|
4269 | if (!Squid_SSL_accept(conn, clientPeekAndSpliceSSL)) |
---|
4270 | debugs(83, 2, "SSL_accept failed."); |
---|
4271 | |
---|
4272 | BIO *b = SSL_get_rbio(ssl); |
---|
4273 | assert(b); |
---|
4274 | Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr); |
---|
4275 | if (bio->gotHello()) { |
---|
4276 | if (conn->serverBump()) { |
---|
4277 | Ssl::Bio::sslFeatures const &features = bio->getFeatures(); |
---|
4278 | if (!features.serverName.isEmpty()) { |
---|
4279 | conn->serverBump()->clientSni = features.serverName; |
---|
4280 | conn->resetSslCommonName(features.serverName.c_str()); |
---|
4281 | } |
---|
4282 | } |
---|
4283 | |
---|
4284 | debugs(83, 5, "I got hello. Start forwarding the request!!! "); |
---|
4285 | Comm::SetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0); |
---|
4286 | Comm::SetSelect(fd, COMM_SELECT_WRITE, NULL, NULL, 0); |
---|
4287 | conn->startPeekAndSpliceDone(); |
---|
4288 | return; |
---|
4289 | } |
---|
4290 | } |
---|
4291 | |
---|
4292 | void ConnStateData::startPeekAndSplice() |
---|
4293 | { |
---|
4294 | // will call httpsPeeked() with certificate and connection, eventually |
---|
4295 | SSL_CTX *unConfiguredCTX = Ssl::createSSLContext(port->signingCert, port->signPkey, *port); |
---|
4296 | fd_table[clientConnection->fd].dynamicSslContext = unConfiguredCTX; |
---|
4297 | |
---|
4298 | if (!httpsCreate(clientConnection, unConfiguredCTX)) |
---|
4299 | return; |
---|
4300 | |
---|
4301 | // commSetConnTimeout() was called for this request before we switched. |
---|
4302 | |
---|
4303 | // Disable the client read handler until CachePeer selection is complete |
---|
4304 | Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0); |
---|
4305 | Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, clientPeekAndSpliceSSL, this, 0); |
---|
4306 | switchedToHttps_ = true; |
---|
4307 | |
---|
4308 | SSL *ssl = fd_table[clientConnection->fd].ssl; |
---|
4309 | BIO *b = SSL_get_rbio(ssl); |
---|
4310 | Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr); |
---|
4311 | bio->hold(true); |
---|
4312 | } |
---|
4313 | |
---|
4314 | void httpsSslBumpStep2AccessCheckDone(allow_t answer, void *data) |
---|
4315 | { |
---|
4316 | ConnStateData *connState = (ConnStateData *) data; |
---|
4317 | |
---|
4318 | // if the connection is closed or closing, just return. |
---|
4319 | if (!connState->isOpen()) |
---|
4320 | return; |
---|
4321 | |
---|
4322 | debugs(33, 5, "Answer: " << answer << " kind:" << answer.kind); |
---|
4323 | assert(connState->serverBump()); |
---|
4324 | Ssl::BumpMode bumpAction; |
---|
4325 | if (answer == ACCESS_ALLOWED) { |
---|
4326 | bumpAction = (Ssl::BumpMode)answer.kind; |
---|
4327 | } else |
---|
4328 | bumpAction = Ssl::bumpSplice; |
---|
4329 | |
---|
4330 | connState->serverBump()->act.step2 = bumpAction; |
---|
4331 | connState->sslBumpMode = bumpAction; |
---|
4332 | |
---|
4333 | if (bumpAction == Ssl::bumpTerminate) { |
---|
4334 | connState->clientConnection->close(); |
---|
4335 | } else if (bumpAction != Ssl::bumpSplice) { |
---|
4336 | connState->startPeekAndSpliceDone(); |
---|
4337 | } else { |
---|
4338 | //Normally we can splice here, because we just got client hello message |
---|
4339 | SSL *ssl = fd_table[connState->clientConnection->fd].ssl; |
---|
4340 | BIO *b = SSL_get_rbio(ssl); |
---|
4341 | Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr); |
---|
4342 | MemBuf const &rbuf = bio->rBufData(); |
---|
4343 | debugs(83,5, "Bio for " << connState->clientConnection << " read " << rbuf.contentSize() << " helo bytes"); |
---|
4344 | // Do splice: |
---|
4345 | fd_table[connState->clientConnection->fd].read_method = &default_read_method; |
---|
4346 | fd_table[connState->clientConnection->fd].write_method = &default_write_method; |
---|
4347 | |
---|
4348 | if (connState->transparent()) { |
---|
4349 | // fake a CONNECT request to force connState to tunnel |
---|
4350 | // XXX: copy from MemBuf reallocates, not a regression since old code did too |
---|
4351 | SBuf temp; |
---|
4352 | temp.append(rbuf.content(), rbuf.contentSize()); |
---|
4353 | connState->fakeAConnectRequest("intercepted TLS spliced", temp); |
---|
4354 | } else { |
---|
4355 | // in.buf still has the "CONNECT ..." request data, reset it to SSL hello message |
---|
4356 | connState->in.buf.append(rbuf.content(), rbuf.contentSize()); |
---|
4357 | ClientSocketContext::Pointer context = connState->getCurrentContext(); |
---|
4358 | ClientHttpRequest *http = context->http; |
---|
4359 | tunnelStart(http, &http->out.size, &http->al->http.code, http->al); |
---|
4360 | } |
---|
4361 | } |
---|
4362 | } |
---|
4363 | |
---|
4364 | void |
---|
4365 | ConnStateData::startPeekAndSpliceDone() |
---|
4366 | { |
---|
4367 | // This is the Step2 of the SSL bumping |
---|
4368 | assert(sslServerBump); |
---|
4369 | if (sslServerBump->step == Ssl::bumpStep1) { |
---|
4370 | sslServerBump->step = Ssl::bumpStep2; |
---|
4371 | // Run a accessList check to check if want to splice or continue bumping |
---|
4372 | |
---|
4373 | ACLFilledChecklist *acl_checklist = new ACLFilledChecklist(Config.accessList.ssl_bump, sslServerBump->request.getRaw(), NULL); |
---|
4374 | //acl_checklist->src_addr = params.conn->remote; |
---|
4375 | //acl_checklist->my_addr = s->s; |
---|
4376 | acl_checklist->banAction(allow_t(ACCESS_ALLOWED, Ssl::bumpNone)); |
---|
4377 | acl_checklist->banAction(allow_t(ACCESS_ALLOWED, Ssl::bumpClientFirst)); |
---|
4378 | acl_checklist->banAction(allow_t(ACCESS_ALLOWED, Ssl::bumpServerFirst)); |
---|
4379 | acl_checklist->nonBlockingCheck(httpsSslBumpStep2AccessCheckDone, this); |
---|
4380 | return; |
---|
4381 | } |
---|
4382 | |
---|
4383 | FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw()); |
---|
4384 | } |
---|
4385 | |
---|
4386 | void |
---|
4387 | ConnStateData::doPeekAndSpliceStep() |
---|
4388 | { |
---|
4389 | SSL *ssl = fd_table[clientConnection->fd].ssl; |
---|
4390 | BIO *b = SSL_get_rbio(ssl); |
---|
4391 | assert(b); |
---|
4392 | Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr); |
---|
4393 | |
---|
4394 | debugs(33, 5, "PeekAndSplice mode, proceed with client negotiation. Currrent state:" << SSL_state_string_long(ssl)); |
---|
4395 | bio->hold(false); |
---|
4396 | |
---|
4397 | Comm::SetSelect(clientConnection->fd, COMM_SELECT_WRITE, clientNegotiateSSL, this, 0); |
---|
4398 | switchedToHttps_ = true; |
---|
4399 | } |
---|
4400 | |
---|
4401 | void |
---|
4402 | ConnStateData::httpsPeeked(Comm::ConnectionPointer serverConnection) |
---|
4403 | { |
---|
4404 | Must(sslServerBump != NULL); |
---|
4405 | |
---|
4406 | if (Comm::IsConnOpen(serverConnection)) { |
---|
4407 | pinConnection(serverConnection, NULL, NULL, false); |
---|
4408 | |
---|
4409 | debugs(33, 5, HERE << "bumped HTTPS server: " << sslConnectHostOrIp); |
---|
4410 | } else { |
---|
4411 | debugs(33, 5, HERE << "Error while bumping: " << sslConnectHostOrIp); |
---|
4412 | |
---|
4413 | // copy error detail from bump-server-first request to CONNECT request |
---|
4414 | if (currentobject != NULL && currentobject->http != NULL && currentobject->http->request) |
---|
4415 | currentobject->http->request->detailError(sslServerBump->request->errType, sslServerBump->request->errDetail); |
---|
4416 | } |
---|
4417 | |
---|
4418 | getSslContextStart(); |
---|
4419 | } |
---|
4420 | |
---|
4421 | #endif /* USE_OPENSSL */ |
---|
4422 | |
---|
4423 | void |
---|
4424 | ConnStateData::fakeAConnectRequest(const char *reason, const SBuf &payload) |
---|
4425 | { |
---|
4426 | // fake a CONNECT request to force connState to tunnel |
---|
4427 | SBuf connectHost; |
---|
4428 | #if USE_OPENSSL |
---|
4429 | if (serverBump() && !serverBump()->clientSni.isEmpty()) { |
---|
4430 | connectHost.assign(serverBump()->clientSni); |
---|
4431 | if (clientConnection->local.port() > 0) |
---|
4432 | connectHost.appendf(":%d",clientConnection->local.port()); |
---|
4433 | } else |
---|
4434 | #endif |
---|
4435 | { |
---|
4436 | static char ip[MAX_IPSTRLEN]; |
---|
4437 | connectHost.assign(clientConnection->local.toUrl(ip, sizeof(ip))); |
---|
4438 | } |
---|
4439 | // Pre-pend this fake request to the TLS bits already in the buffer |
---|
4440 | SBuf retStr; |
---|
4441 | retStr.append("CONNECT "); |
---|
4442 | retStr.append(connectHost); |
---|
4443 | retStr.append(" HTTP/1.1\r\nHost: "); |
---|
4444 | retStr.append(connectHost); |
---|
4445 | retStr.append("\r\n\r\n"); |
---|
4446 | retStr.append(payload); |
---|
4447 | in.buf = retStr; |
---|
4448 | bool ret = handleReadData(); |
---|
4449 | if (ret) |
---|
4450 | ret = clientParseRequests(); |
---|
4451 | |
---|
4452 | if (!ret) { |
---|
4453 | debugs(33, 2, "Failed to start fake CONNECT request for " << reason << " connection: " << clientConnection); |
---|
4454 | clientConnection->close(); |
---|
4455 | } |
---|
4456 | } |
---|
4457 | |
---|
4458 | /// check FD after clientHttp[s]ConnectionOpened, adjust HttpSockets as needed |
---|
4459 | static bool |
---|
4460 | OpenedHttpSocket(const Comm::ConnectionPointer &c, const Ipc::FdNoteId portType) |
---|
4461 | { |
---|
4462 | if (!Comm::IsConnOpen(c)) { |
---|
4463 | Must(NHttpSockets > 0); // we tried to open some |
---|
4464 | --NHttpSockets; // there will be fewer sockets than planned |
---|
4465 | Must(HttpSockets[NHttpSockets] < 0); // no extra fds received |
---|
4466 | |
---|
4467 | if (!NHttpSockets) // we could not open any listen sockets at all |
---|
4468 | fatalf("Unable to open %s",FdNote(portType)); |
---|
4469 | |
---|
4470 | return false; |
---|
4471 | } |
---|
4472 | return true; |
---|
4473 | } |
---|
4474 | |
---|
4475 | /// find any unused HttpSockets[] slot and store fd there or return false |
---|
4476 | static bool |
---|
4477 | AddOpenedHttpSocket(const Comm::ConnectionPointer &conn) |
---|
4478 | { |
---|
4479 | bool found = false; |
---|
4480 | for (int i = 0; i < NHttpSockets && !found; ++i) { |
---|
4481 | if ((found = HttpSockets[i] < 0)) |
---|
4482 | HttpSockets[i] = conn->fd; |
---|
4483 | } |
---|
4484 | return found; |
---|
4485 | } |
---|
4486 | |
---|
4487 | static void |
---|
4488 | clientHttpConnectionsOpen(void) |
---|
4489 | { |
---|
4490 | for (AnyP::PortCfgPointer s = HttpPortList; s != NULL; s = s->next) { |
---|
4491 | if (MAXTCPLISTENPORTS == NHttpSockets) { |
---|
4492 | debugs(1, DBG_IMPORTANT, "WARNING: You have too many 'http_port' lines."); |
---|
4493 | debugs(1, DBG_IMPORTANT, " The limit is " << MAXTCPLISTENPORTS << " HTTP ports."); |
---|
4494 | continue; |
---|
4495 | } |
---|
4496 | |
---|
4497 | #if USE_OPENSSL |
---|
4498 | if (s->flags.tunnelSslBumping && !Config.accessList.ssl_bump) { |
---|
4499 | debugs(33, DBG_IMPORTANT, "WARNING: No ssl_bump configured. Disabling ssl-bump on " << AnyP::UriScheme(s->transport.protocol) << "_port " << s->s); |
---|
4500 | s->flags.tunnelSslBumping = false; |
---|
4501 | } |
---|
4502 | |
---|
4503 | if (s->flags.tunnelSslBumping && |
---|
4504 | !s->staticSslContext && |
---|
4505 | !s->generateHostCertificates) { |
---|
4506 | debugs(1, DBG_IMPORTANT, "Will not bump SSL at http_port " << s->s << " due to SSL initialization failure."); |
---|
4507 | s->flags.tunnelSslBumping = false; |
---|
4508 | } |
---|
4509 | if (s->flags.tunnelSslBumping) { |
---|
4510 | // Create ssl_ctx cache for this port. |
---|
4511 | Ssl::TheGlobalContextStorage.addLocalStorage(s->s, s->dynamicCertMemCacheSize == std::numeric_limits<size_t>::max() ? 4194304 : s->dynamicCertMemCacheSize); |
---|
4512 | } |
---|
4513 | #endif |
---|
4514 | |
---|
4515 | // Fill out a Comm::Connection which IPC will open as a listener for us |
---|
4516 | // then pass back when active so we can start a TcpAcceptor subscription. |
---|
4517 | s->listenConn = new Comm::Connection; |
---|
4518 | s->listenConn->local = s->s; |
---|
4519 | s->listenConn->flags = COMM_NONBLOCKING | (s->flags.tproxyIntercept ? COMM_TRANSPARENT : 0) | (s->flags.natIntercept ? COMM_INTERCEPTION : 0); |
---|
4520 | |
---|
4521 | // setup the subscriptions such that new connections accepted by listenConn are handled by HTTP |
---|
4522 | typedef CommCbFunPtrCallT<CommAcceptCbPtrFun> AcceptCall; |
---|
4523 | RefCount<AcceptCall> subCall = commCbCall(5, 5, "httpAccept", CommAcceptCbPtrFun(httpAccept, CommAcceptCbParams(NULL))); |
---|
4524 | Subscription::Pointer sub = new CallSubscription<AcceptCall>(subCall); |
---|
4525 | |
---|
4526 | AsyncCall::Pointer listenCall = asyncCall(33,2, "clientListenerConnectionOpened", |
---|
4527 | ListeningStartedDialer(&clientListenerConnectionOpened, s, Ipc::fdnHttpSocket, sub)); |
---|
4528 | Ipc::StartListening(SOCK_STREAM, IPPROTO_TCP, s->listenConn, Ipc::fdnHttpSocket, listenCall); |
---|
4529 | |
---|
4530 | HttpSockets[NHttpSockets] = -1; // set in clientListenerConnectionOpened |
---|
4531 | ++NHttpSockets; |
---|
4532 | } |
---|
4533 | } |
---|
4534 | |
---|
4535 | #if USE_OPENSSL |
---|
4536 | static void |
---|
4537 | clientHttpsConnectionsOpen(void) |
---|
4538 | { |
---|
4539 | for (AnyP::PortCfgPointer s = HttpsPortList; s != NULL; s = s->next) { |
---|
4540 | if (MAXTCPLISTENPORTS == NHttpSockets) { |
---|
4541 | debugs(1, DBG_IMPORTANT, "Ignoring 'https_port' lines exceeding the limit."); |
---|
4542 | debugs(1, DBG_IMPORTANT, "The limit is " << MAXTCPLISTENPORTS << " HTTPS ports."); |
---|
4543 | continue; |
---|
4544 | } |
---|
4545 | |
---|
4546 | if (!s->staticSslContext) { |
---|
4547 | debugs(1, DBG_IMPORTANT, "Ignoring https_port " << s->s << |
---|
4548 | " due to SSL initialization failure."); |
---|
4549 | continue; |
---|
4550 | } |
---|
4551 | |
---|
4552 | // TODO: merge with similar code in clientHttpConnectionsOpen() |
---|
4553 | if (s->flags.tunnelSslBumping && !Config.accessList.ssl_bump) { |
---|
4554 | debugs(33, DBG_IMPORTANT, "WARNING: No ssl_bump configured. Disabling ssl-bump on " << AnyP::UriScheme(s->transport.protocol) << "_port " << s->s); |
---|
4555 | s->flags.tunnelSslBumping = false; |
---|
4556 | } |
---|
4557 | |
---|
4558 | if (s->flags.tunnelSslBumping && !s->staticSslContext && !s->generateHostCertificates) { |
---|
4559 | debugs(1, DBG_IMPORTANT, "Will not bump SSL at https_port " << s->s << " due to SSL initialization failure."); |
---|
4560 | s->flags.tunnelSslBumping = false; |
---|
4561 | } |
---|
4562 | |
---|
4563 | if (s->flags.tunnelSslBumping) { |
---|
4564 | // Create ssl_ctx cache for this port. |
---|
4565 | Ssl::TheGlobalContextStorage.addLocalStorage(s->s, s->dynamicCertMemCacheSize == std::numeric_limits<size_t>::max() ? 4194304 : s->dynamicCertMemCacheSize); |
---|
4566 | } |
---|
4567 | |
---|
4568 | // Fill out a Comm::Connection which IPC will open as a listener for us |
---|
4569 | s->listenConn = new Comm::Connection; |
---|
4570 | s->listenConn->local = s->s; |
---|
4571 | s->listenConn->flags = COMM_NONBLOCKING | (s->flags.tproxyIntercept ? COMM_TRANSPARENT : 0) | |
---|
4572 | (s->flags.natIntercept ? COMM_INTERCEPTION : 0); |
---|
4573 | |
---|
4574 | // setup the subscriptions such that new connections accepted by listenConn are handled by HTTPS |
---|
4575 | typedef CommCbFunPtrCallT<CommAcceptCbPtrFun> AcceptCall; |
---|
4576 | RefCount<AcceptCall> subCall = commCbCall(5, 5, "httpsAccept", CommAcceptCbPtrFun(httpsAccept, CommAcceptCbParams(NULL))); |
---|
4577 | Subscription::Pointer sub = new CallSubscription<AcceptCall>(subCall); |
---|
4578 | |
---|
4579 | AsyncCall::Pointer listenCall = asyncCall(33, 2, "clientListenerConnectionOpened", |
---|
4580 | ListeningStartedDialer(&clientListenerConnectionOpened, |
---|
4581 | s, Ipc::fdnHttpsSocket, sub)); |
---|
4582 | Ipc::StartListening(SOCK_STREAM, IPPROTO_TCP, s->listenConn, Ipc::fdnHttpsSocket, listenCall); |
---|
4583 | HttpSockets[NHttpSockets] = -1; |
---|
4584 | ++NHttpSockets; |
---|
4585 | } |
---|
4586 | } |
---|
4587 | #endif |
---|
4588 | |
---|
4589 | void |
---|
4590 | clientStartListeningOn(AnyP::PortCfgPointer &port, const RefCount< CommCbFunPtrCallT<CommAcceptCbPtrFun> > &subCall, const Ipc::FdNoteId fdNote) |
---|
4591 | { |
---|
4592 | // Fill out a Comm::Connection which IPC will open as a listener for us |
---|
4593 | port->listenConn = new Comm::Connection; |
---|
4594 | port->listenConn->local = port->s; |
---|
4595 | port->listenConn->flags = |
---|
4596 | COMM_NONBLOCKING | |
---|
4597 | (port->flags.tproxyIntercept ? COMM_TRANSPARENT : 0) | |
---|
4598 | (port->flags.natIntercept ? COMM_INTERCEPTION : 0); |
---|
4599 | |
---|
4600 | // route new connections to subCall |
---|
4601 | typedef CommCbFunPtrCallT<CommAcceptCbPtrFun> AcceptCall; |
---|
4602 | Subscription::Pointer sub = new CallSubscription<AcceptCall>(subCall); |
---|
4603 | AsyncCall::Pointer listenCall = |
---|
4604 | asyncCall(33, 2, "clientListenerConnectionOpened", |
---|
4605 | ListeningStartedDialer(&clientListenerConnectionOpened, |
---|
4606 | port, fdNote, sub)); |
---|
4607 | Ipc::StartListening(SOCK_STREAM, IPPROTO_TCP, port->listenConn, fdNote, listenCall); |
---|
4608 | |
---|
4609 | assert(NHttpSockets < MAXTCPLISTENPORTS); |
---|
4610 | HttpSockets[NHttpSockets] = -1; |
---|
4611 | ++NHttpSockets; |
---|
4612 | } |
---|
4613 | |
---|
4614 | /// process clientHttpConnectionsOpen result |
---|
4615 | static void |
---|
4616 | clientListenerConnectionOpened(AnyP::PortCfgPointer &s, const Ipc::FdNoteId portTypeNote, const Subscription::Pointer &sub) |
---|
4617 | { |
---|
4618 | Must(s != NULL); |
---|
4619 | |
---|
4620 | if (!OpenedHttpSocket(s->listenConn, portTypeNote)) |
---|
4621 | return; |
---|
4622 | |
---|
4623 | Must(Comm::IsConnOpen(s->listenConn)); |
---|
4624 | |
---|
4625 | // TCP: setup a job to handle accept() with subscribed handler |
---|
4626 | AsyncJob::Start(new Comm::TcpAcceptor(s, FdNote(portTypeNote), sub)); |
---|
4627 | |
---|
4628 | debugs(1, DBG_IMPORTANT, "Accepting " << |
---|
4629 | (s->flags.natIntercept ? "NAT intercepted " : "") << |
---|
4630 | (s->flags.tproxyIntercept ? "TPROXY intercepted " : "") << |
---|
4631 | (s->flags.tunnelSslBumping ? "SSL bumped " : "") << |
---|
4632 | (s->flags.accelSurrogate ? "reverse-proxy " : "") |
---|
4633 | << FdNote(portTypeNote) << " connections at " |
---|
4634 | << s->listenConn); |
---|
4635 | |
---|
4636 | Must(AddOpenedHttpSocket(s->listenConn)); // otherwise, we have received a fd we did not ask for |
---|
4637 | } |
---|
4638 | |
---|
4639 | void |
---|
4640 | clientOpenListenSockets(void) |
---|
4641 | { |
---|
4642 | clientHttpConnectionsOpen(); |
---|
4643 | #if USE_OPENSSL |
---|
4644 | clientHttpsConnectionsOpen(); |
---|
4645 | #endif |
---|
4646 | Ftp::StartListening(); |
---|
4647 | |
---|
4648 | if (NHttpSockets < 1) |
---|
4649 | fatal("No HTTP, HTTPS, or FTP ports configured"); |
---|
4650 | } |
---|
4651 | |
---|
4652 | void |
---|
4653 | clientConnectionsClose() |
---|
4654 | { |
---|
4655 | for (AnyP::PortCfgPointer s = HttpPortList; s != NULL; s = s->next) { |
---|
4656 | if (s->listenConn != NULL) { |
---|
4657 | debugs(1, DBG_IMPORTANT, "Closing HTTP port " << s->listenConn->local); |
---|
4658 | s->listenConn->close(); |
---|
4659 | s->listenConn = NULL; |
---|
4660 | } |
---|
4661 | } |
---|
4662 | |
---|
4663 | #if USE_OPENSSL |
---|
4664 | for (AnyP::PortCfgPointer s = HttpsPortList; s != NULL; s = s->next) { |
---|
4665 | if (s->listenConn != NULL) { |
---|
4666 | debugs(1, DBG_IMPORTANT, "Closing HTTPS port " << s->listenConn->local); |
---|
4667 | s->listenConn->close(); |
---|
4668 | s->listenConn = NULL; |
---|
4669 | } |
---|
4670 | } |
---|
4671 | #endif |
---|
4672 | |
---|
4673 | Ftp::StopListening(); |
---|
4674 | |
---|
4675 | // TODO see if we can drop HttpSockets array entirely */ |
---|
4676 | for (int i = 0; i < NHttpSockets; ++i) { |
---|
4677 | HttpSockets[i] = -1; |
---|
4678 | } |
---|
4679 | |
---|
4680 | NHttpSockets = 0; |
---|
4681 | } |
---|
4682 | |
---|
4683 | int |
---|
4684 | varyEvaluateMatch(StoreEntry * entry, HttpRequest * request) |
---|
4685 | { |
---|
4686 | const char *vary = request->vary_headers; |
---|
4687 | int has_vary = entry->getReply()->header.has(HDR_VARY); |
---|
4688 | #if X_ACCELERATOR_VARY |
---|
4689 | |
---|
4690 | has_vary |= |
---|
4691 | entry->getReply()->header.has(HDR_X_ACCELERATOR_VARY); |
---|
4692 | #endif |
---|
4693 | |
---|
4694 | if (!has_vary || !entry->mem_obj->vary_headers) { |
---|
4695 | if (vary) { |
---|
4696 | /* Oops... something odd is going on here.. */ |
---|
4697 | debugs(33, DBG_IMPORTANT, "varyEvaluateMatch: Oops. Not a Vary object on second attempt, '" << |
---|
4698 | entry->mem_obj->urlXXX() << "' '" << vary << "'"); |
---|
4699 | safe_free(request->vary_headers); |
---|
4700 | return VARY_CANCEL; |
---|
4701 | } |
---|
4702 | |
---|
4703 | if (!has_vary) { |
---|
4704 | /* This is not a varying object */ |
---|
4705 | return VARY_NONE; |
---|
4706 | } |
---|
4707 | |
---|
4708 | /* virtual "vary" object found. Calculate the vary key and |
---|
4709 | * continue the search |
---|
4710 | */ |
---|
4711 | vary = httpMakeVaryMark(request, entry->getReply()); |
---|
4712 | |
---|
4713 | if (vary) { |
---|
4714 | request->vary_headers = xstrdup(vary); |
---|
4715 | return VARY_OTHER; |
---|
4716 | } else { |
---|
4717 | /* Ouch.. we cannot handle this kind of variance */ |
---|
4718 | /* XXX This cannot really happen, but just to be complete */ |
---|
4719 | return VARY_CANCEL; |
---|
4720 | } |
---|
4721 | } else { |
---|
4722 | if (!vary) { |
---|
4723 | vary = httpMakeVaryMark(request, entry->getReply()); |
---|
4724 | |
---|
4725 | if (vary) |
---|
4726 | request->vary_headers = xstrdup(vary); |
---|
4727 | } |
---|
4728 | |
---|
4729 | if (!vary) { |
---|
4730 | /* Ouch.. we cannot handle this kind of variance */ |
---|
4731 | /* XXX This cannot really happen, but just to be complete */ |
---|
4732 | return VARY_CANCEL; |
---|
4733 | } else if (strcmp(vary, entry->mem_obj->vary_headers) == 0) { |
---|
4734 | return VARY_MATCH; |
---|
4735 | } else { |
---|
4736 | /* Oops.. we have already been here and still haven't |
---|
4737 | * found the requested variant. Bail out |
---|
4738 | */ |
---|
4739 | debugs(33, DBG_IMPORTANT, "varyEvaluateMatch: Oops. Not a Vary match on second attempt, '" << |
---|
4740 | entry->mem_obj->urlXXX() << "' '" << vary << "'"); |
---|
4741 | return VARY_CANCEL; |
---|
4742 | } |
---|
4743 | } |
---|
4744 | } |
---|
4745 | |
---|
4746 | ACLFilledChecklist * |
---|
4747 | clientAclChecklistCreate(const acl_access * acl, ClientHttpRequest * http) |
---|
4748 | { |
---|
4749 | ConnStateData * conn = http->getConn(); |
---|
4750 | ACLFilledChecklist *ch = new ACLFilledChecklist(acl, http->request, |
---|
4751 | cbdataReferenceValid(conn) && conn != NULL && conn->clientConnection != NULL ? conn->clientConnection->rfc931 : dash_str); |
---|
4752 | ch->al = http->al; |
---|
4753 | /* |
---|
4754 | * hack for ident ACL. It needs to get full addresses, and a place to store |
---|
4755 | * the ident result on persistent connections... |
---|
4756 | */ |
---|
4757 | /* connection oriented auth also needs these two lines for it's operation. */ |
---|
4758 | return ch; |
---|
4759 | } |
---|
4760 | |
---|
4761 | bool |
---|
4762 | ConnStateData::transparent() const |
---|
4763 | { |
---|
4764 | return clientConnection != NULL && (clientConnection->flags & (COMM_TRANSPARENT|COMM_INTERCEPTION)); |
---|
4765 | } |
---|
4766 | |
---|
4767 | bool |
---|
4768 | ConnStateData::reading() const |
---|
4769 | { |
---|
4770 | return reader != NULL; |
---|
4771 | } |
---|
4772 | |
---|
4773 | void |
---|
4774 | ConnStateData::stopReading() |
---|
4775 | { |
---|
4776 | if (reading()) { |
---|
4777 | Comm::ReadCancel(clientConnection->fd, reader); |
---|
4778 | reader = NULL; |
---|
4779 | } |
---|
4780 | } |
---|
4781 | |
---|
4782 | BodyPipe::Pointer |
---|
4783 | ConnStateData::expectRequestBody(int64_t size) |
---|
4784 | { |
---|
4785 | bodyPipe = new BodyPipe(this); |
---|
4786 | if (size >= 0) |
---|
4787 | bodyPipe->setBodySize(size); |
---|
4788 | else |
---|
4789 | startDechunkingRequest(); |
---|
4790 | return bodyPipe; |
---|
4791 | } |
---|
4792 | |
---|
4793 | int64_t |
---|
4794 | ConnStateData::mayNeedToReadMoreBody() const |
---|
4795 | { |
---|
4796 | if (!bodyPipe) |
---|
4797 | return 0; // request without a body or read/produced all body bytes |
---|
4798 | |
---|
4799 | if (!bodyPipe->bodySizeKnown()) |
---|
4800 | return -1; // probably need to read more, but we cannot be sure |
---|
4801 | |
---|
4802 | const int64_t needToProduce = bodyPipe->unproducedSize(); |
---|
4803 | const int64_t haveAvailable = static_cast<int64_t>(in.buf.length()); |
---|
4804 | |
---|
4805 | if (needToProduce <= haveAvailable) |
---|
4806 | return 0; // we have read what we need (but are waiting for pipe space) |
---|
4807 | |
---|
4808 | return needToProduce - haveAvailable; |
---|
4809 | } |
---|
4810 | |
---|
4811 | void |
---|
4812 | ConnStateData::stopReceiving(const char *error) |
---|
4813 | { |
---|
4814 | debugs(33, 4, HERE << "receiving error (" << clientConnection << "): " << error << |
---|
4815 | "; old sending error: " << |
---|
4816 | (stoppedSending() ? stoppedSending_ : "none")); |
---|
4817 | |
---|
4818 | if (const char *oldError = stoppedReceiving()) { |
---|
4819 | debugs(33, 3, HERE << "already stopped receiving: " << oldError); |
---|
4820 | return; // nothing has changed as far as this connection is concerned |
---|
4821 | } |
---|
4822 | |
---|
4823 | stoppedReceiving_ = error; |
---|
4824 | |
---|
4825 | if (const char *sendError = stoppedSending()) { |
---|
4826 | debugs(33, 3, HERE << "closing because also stopped sending: " << sendError); |
---|
4827 | clientConnection->close(); |
---|
4828 | } |
---|
4829 | } |
---|
4830 | |
---|
4831 | void |
---|
4832 | ConnStateData::expectNoForwarding() |
---|
4833 | { |
---|
4834 | if (bodyPipe != NULL) { |
---|
4835 | debugs(33, 4, HERE << "no consumer for virgin body " << bodyPipe->status()); |
---|
4836 | bodyPipe->expectNoConsumption(); |
---|
4837 | } |
---|
4838 | } |
---|
4839 | |
---|
4840 | /// initialize dechunking state |
---|
4841 | void |
---|
4842 | ConnStateData::startDechunkingRequest() |
---|
4843 | { |
---|
4844 | Must(bodyPipe != NULL); |
---|
4845 | debugs(33, 5, HERE << "start dechunking" << bodyPipe->status()); |
---|
4846 | assert(!in.bodyParser); |
---|
4847 | in.bodyParser = new ChunkedCodingParser; |
---|
4848 | } |
---|
4849 | |
---|
4850 | /// put parsed content into input buffer and clean up |
---|
4851 | void |
---|
4852 | ConnStateData::finishDechunkingRequest(bool withSuccess) |
---|
4853 | { |
---|
4854 | debugs(33, 5, HERE << "finish dechunking: " << withSuccess); |
---|
4855 | |
---|
4856 | if (bodyPipe != NULL) { |
---|
4857 | debugs(33, 7, HERE << "dechunked tail: " << bodyPipe->status()); |
---|
4858 | BodyPipe::Pointer myPipe = bodyPipe; |
---|
4859 | stopProducingFor(bodyPipe, withSuccess); // sets bodyPipe->bodySize() |
---|
4860 | Must(!bodyPipe); // we rely on it being nil after we are done with body |
---|
4861 | if (withSuccess) { |
---|
4862 | Must(myPipe->bodySizeKnown()); |
---|
4863 | ClientSocketContext::Pointer context = getCurrentContext(); |
---|
4864 | if (context != NULL && context->http && context->http->request) |
---|
4865 | context->http->request->setContentLength(myPipe->bodySize()); |
---|
4866 | } |
---|
4867 | } |
---|
4868 | |
---|
4869 | delete in.bodyParser; |
---|
4870 | in.bodyParser = NULL; |
---|
4871 | } |
---|
4872 | |
---|
4873 | ConnStateData::In::In() : |
---|
4874 | bodyParser(NULL), |
---|
4875 | buf() |
---|
4876 | {} |
---|
4877 | |
---|
4878 | ConnStateData::In::~In() |
---|
4879 | { |
---|
4880 | delete bodyParser; // TODO: pool |
---|
4881 | } |
---|
4882 | |
---|
4883 | void |
---|
4884 | ConnStateData::sendControlMsg(HttpControlMsg msg) |
---|
4885 | { |
---|
4886 | if (!isOpen()) { |
---|
4887 | debugs(33, 3, HERE << "ignoring 1xx due to earlier closure"); |
---|
4888 | return; |
---|
4889 | } |
---|
4890 | |
---|
4891 | ClientSocketContext::Pointer context = getCurrentContext(); |
---|
4892 | if (context != NULL) { |
---|
4893 | context->writeControlMsg(msg); // will call msg.cbSuccess |
---|
4894 | return; |
---|
4895 | } |
---|
4896 | |
---|
4897 | debugs(33, 3, HERE << " closing due to missing context for 1xx"); |
---|
4898 | clientConnection->close(); |
---|
4899 | } |
---|
4900 | |
---|
4901 | /// Our close handler called by Comm when the pinned connection is closed |
---|
4902 | void |
---|
4903 | ConnStateData::clientPinnedConnectionClosed(const CommCloseCbParams &io) |
---|
4904 | { |
---|
4905 | // FwdState might repin a failed connection sooner than this close |
---|
4906 | // callback is called for the failed connection. |
---|
4907 | assert(pinning.serverConnection == io.conn); |
---|
4908 | pinning.closeHandler = NULL; // Comm unregisters handlers before calling |
---|
4909 | const bool sawZeroReply = pinning.zeroReply; // reset when unpinning |
---|
4910 | pinning.serverConnection->noteClosure(); |
---|
4911 | unpinConnection(false); |
---|
4912 | |
---|
4913 | if (sawZeroReply && clientConnection != NULL) { |
---|
4914 | debugs(33, 3, "Closing client connection on pinned zero reply."); |
---|
4915 | clientConnection->close(); |
---|
4916 | } |
---|
4917 | |
---|
4918 | } |
---|
4919 | |
---|
4920 | void |
---|
4921 | ConnStateData::pinConnection(const Comm::ConnectionPointer &pinServer, HttpRequest *request, CachePeer *aPeer, bool auth, bool monitor) |
---|
4922 | { |
---|
4923 | if (!Comm::IsConnOpen(pinning.serverConnection) || |
---|
4924 | pinning.serverConnection->fd != pinServer->fd) |
---|
4925 | pinNewConnection(pinServer, request, aPeer, auth); |
---|
4926 | |
---|
4927 | if (monitor) |
---|
4928 | startPinnedConnectionMonitoring(); |
---|
4929 | } |
---|
4930 | |
---|
4931 | void |
---|
4932 | ConnStateData::pinNewConnection(const Comm::ConnectionPointer &pinServer, HttpRequest *request, CachePeer *aPeer, bool auth) |
---|
4933 | { |
---|
4934 | unpinConnection(true); // closes pinned connection, if any, and resets fields |
---|
4935 | |
---|
4936 | pinning.serverConnection = pinServer; |
---|
4937 | |
---|
4938 | debugs(33, 3, HERE << pinning.serverConnection); |
---|
4939 | |
---|
4940 | Must(pinning.serverConnection != NULL); |
---|
4941 | |
---|
4942 | // when pinning an SSL bumped connection, the request may be NULL |
---|
4943 | const char *pinnedHost = "[unknown]"; |
---|
4944 | if (request) { |
---|
4945 | pinning.host = xstrdup(request->GetHost()); |
---|
4946 | pinning.port = request->port; |
---|
4947 | pinnedHost = pinning.host; |
---|
4948 | } else { |
---|
4949 | pinning.port = pinServer->remote.port(); |
---|
4950 | } |
---|
4951 | pinning.pinned = true; |
---|
4952 | if (aPeer) |
---|
4953 | pinning.peer = cbdataReference(aPeer); |
---|
4954 | pinning.auth = auth; |
---|
4955 | char stmp[MAX_IPSTRLEN]; |
---|
4956 | char desc[FD_DESC_SZ]; |
---|
4957 | snprintf(desc, FD_DESC_SZ, "%s pinned connection for %s (%d)", |
---|
4958 | (auth || !aPeer) ? pinnedHost : aPeer->name, |
---|
4959 | clientConnection->remote.toUrl(stmp,MAX_IPSTRLEN), |
---|
4960 | clientConnection->fd); |
---|
4961 | fd_note(pinning.serverConnection->fd, desc); |
---|
4962 | |
---|
4963 | typedef CommCbMemFunT<ConnStateData, CommCloseCbParams> Dialer; |
---|
4964 | pinning.closeHandler = JobCallback(33, 5, |
---|
4965 | Dialer, this, ConnStateData::clientPinnedConnectionClosed); |
---|
4966 | // remember the pinned connection so that cb does not unpin a fresher one |
---|
4967 | typedef CommCloseCbParams Params; |
---|
4968 | Params ¶ms = GetCommParams<Params>(pinning.closeHandler); |
---|
4969 | params.conn = pinning.serverConnection; |
---|
4970 | comm_add_close_handler(pinning.serverConnection->fd, pinning.closeHandler); |
---|
4971 | } |
---|
4972 | |
---|
4973 | /// [re]start monitoring pinned connection for peer closures so that we can |
---|
4974 | /// propagate them to an _idle_ client pinned to that peer |
---|
4975 | void |
---|
4976 | ConnStateData::startPinnedConnectionMonitoring() |
---|
4977 | { |
---|
4978 | if (pinning.readHandler != NULL) |
---|
4979 | return; // already monitoring |
---|
4980 | |
---|
4981 | typedef CommCbMemFunT<ConnStateData, CommIoCbParams> Dialer; |
---|
4982 | pinning.readHandler = JobCallback(33, 3, |
---|
4983 | Dialer, this, ConnStateData::clientPinnedConnectionRead); |
---|
4984 | Comm::Read(pinning.serverConnection, pinning.readHandler); |
---|
4985 | } |
---|
4986 | |
---|
4987 | void |
---|
4988 | ConnStateData::stopPinnedConnectionMonitoring() |
---|
4989 | { |
---|
4990 | if (pinning.readHandler != NULL) { |
---|
4991 | Comm::ReadCancel(pinning.serverConnection->fd, pinning.readHandler); |
---|
4992 | pinning.readHandler = NULL; |
---|
4993 | } |
---|
4994 | } |
---|
4995 | |
---|
4996 | #if USE_OPENSSL |
---|
4997 | bool |
---|
4998 | ConnStateData::handleIdleClientPinnedTlsRead() |
---|
4999 | { |
---|
5000 | // A ready-for-reading connection means that the TLS server either closed |
---|
5001 | // the connection, sent us some unexpected HTTP data, or started TLS |
---|
5002 | // renegotiations. We should close the connection except for the last case. |
---|
5003 | |
---|
5004 | Must(pinning.serverConnection != NULL); |
---|
5005 | SSL *ssl = fd_table[pinning.serverConnection->fd].ssl; |
---|
5006 | if (!ssl) |
---|
5007 | return false; |
---|
5008 | |
---|
5009 | char buf[1]; |
---|
5010 | const int readResult = SSL_read(ssl, buf, sizeof(buf)); |
---|
5011 | |
---|
5012 | if (readResult > 0 || SSL_pending(ssl) > 0) { |
---|
5013 | debugs(83, 2, pinning.serverConnection << " TLS application data read"); |
---|
5014 | return false; |
---|
5015 | } |
---|
5016 | |
---|
5017 | switch(const int error = SSL_get_error(ssl, readResult)) { |
---|
5018 | case SSL_ERROR_WANT_WRITE: |
---|
5019 | debugs(83, DBG_IMPORTANT, pinning.serverConnection << " TLS SSL_ERROR_WANT_WRITE request for idle pinned connection"); |
---|
5020 | // fall through to restart monitoring, for now |
---|
5021 | case SSL_ERROR_NONE: |
---|
5022 | case SSL_ERROR_WANT_READ: |
---|
5023 | startPinnedConnectionMonitoring(); |
---|
5024 | return true; |
---|
5025 | |
---|
5026 | default: |
---|
5027 | debugs(83, 2, pinning.serverConnection << " TLS error: " << error); |
---|
5028 | return false; |
---|
5029 | } |
---|
5030 | |
---|
5031 | // not reached |
---|
5032 | return true; |
---|
5033 | } |
---|
5034 | #endif |
---|
5035 | |
---|
5036 | /// Our read handler called by Comm when the server either closes an idle pinned connection or |
---|
5037 | /// perhaps unexpectedly sends something on that idle (from Squid p.o.v.) connection. |
---|
5038 | void |
---|
5039 | ConnStateData::clientPinnedConnectionRead(const CommIoCbParams &io) |
---|
5040 | { |
---|
5041 | pinning.readHandler = NULL; // Comm unregisters handlers before calling |
---|
5042 | |
---|
5043 | if (io.flag == Comm::ERR_CLOSING) |
---|
5044 | return; // close handler will clean up |
---|
5045 | |
---|
5046 | Must(pinning.serverConnection == io.conn); |
---|
5047 | |
---|
5048 | #if USE_OPENSSL |
---|
5049 | if (handleIdleClientPinnedTlsRead()) |
---|
5050 | return; |
---|
5051 | #endif |
---|
5052 | |
---|
5053 | // We could use getConcurrentRequestCount(), but this may be faster. |
---|
5054 | const bool clientIsIdle = !getCurrentContext(); |
---|
5055 | |
---|
5056 | debugs(33, 3, "idle pinned " << pinning.serverConnection << " read " << |
---|
5057 | io.size << (clientIsIdle ? " with idle client" : "")); |
---|
5058 | |
---|
5059 | pinning.serverConnection->close(); |
---|
5060 | |
---|
5061 | // If we are still sending data to the client, do not close now. When we are done sending, |
---|
5062 | // ClientSocketContext::keepaliveNextRequest() checks pinning.serverConnection and will close. |
---|
5063 | // However, if we are idle, then we must close to inform the idle client and minimize races. |
---|
5064 | if (clientIsIdle && clientConnection != NULL) |
---|
5065 | clientConnection->close(); |
---|
5066 | } |
---|
5067 | |
---|
5068 | const Comm::ConnectionPointer |
---|
5069 | ConnStateData::validatePinnedConnection(HttpRequest *request, const CachePeer *aPeer) |
---|
5070 | { |
---|
5071 | debugs(33, 7, HERE << pinning.serverConnection); |
---|
5072 | |
---|
5073 | bool valid = true; |
---|
5074 | if (!Comm::IsConnOpen(pinning.serverConnection)) |
---|
5075 | valid = false; |
---|
5076 | else if (pinning.auth && pinning.host && request && strcasecmp(pinning.host, request->GetHost()) != 0) |
---|
5077 | valid = false; |
---|
5078 | else if (request && pinning.port != request->port) |
---|
5079 | valid = false; |
---|
5080 | else if (pinning.peer && !cbdataReferenceValid(pinning.peer)) |
---|
5081 | valid = false; |
---|
5082 | else if (aPeer != pinning.peer) |
---|
5083 | valid = false; |
---|
5084 | |
---|
5085 | if (!valid) { |
---|
5086 | /* The pinning info is not safe, remove any pinning info */ |
---|
5087 | unpinConnection(true); |
---|
5088 | } |
---|
5089 | |
---|
5090 | return pinning.serverConnection; |
---|
5091 | } |
---|
5092 | |
---|
5093 | Comm::ConnectionPointer |
---|
5094 | ConnStateData::borrowPinnedConnection(HttpRequest *request, const CachePeer *aPeer) |
---|
5095 | { |
---|
5096 | debugs(33, 7, pinning.serverConnection); |
---|
5097 | if (validatePinnedConnection(request, aPeer) != NULL) |
---|
5098 | stopPinnedConnectionMonitoring(); |
---|
5099 | |
---|
5100 | return pinning.serverConnection; // closed if validation failed |
---|
5101 | } |
---|
5102 | |
---|
5103 | void |
---|
5104 | ConnStateData::unpinConnection(const bool andClose) |
---|
5105 | { |
---|
5106 | debugs(33, 3, HERE << pinning.serverConnection); |
---|
5107 | |
---|
5108 | if (pinning.peer) |
---|
5109 | cbdataReferenceDone(pinning.peer); |
---|
5110 | |
---|
5111 | if (Comm::IsConnOpen(pinning.serverConnection)) { |
---|
5112 | if (pinning.closeHandler != NULL) { |
---|
5113 | comm_remove_close_handler(pinning.serverConnection->fd, pinning.closeHandler); |
---|
5114 | pinning.closeHandler = NULL; |
---|
5115 | } |
---|
5116 | |
---|
5117 | stopPinnedConnectionMonitoring(); |
---|
5118 | |
---|
5119 | // close the server side socket if requested |
---|
5120 | if (andClose) |
---|
5121 | pinning.serverConnection->close(); |
---|
5122 | pinning.serverConnection = NULL; |
---|
5123 | } |
---|
5124 | |
---|
5125 | safe_free(pinning.host); |
---|
5126 | |
---|
5127 | pinning.zeroReply = false; |
---|
5128 | |
---|
5129 | /* NOTE: pinning.pinned should be kept. This combined with fd == -1 at the end of a request indicates that the host |
---|
5130 | * connection has gone away */ |
---|
5131 | } |
---|
5132 | |
---|