From: Amos Jeffries Date: Tue, 2 Sep 2014 01:08:58 +0000 (-0700) Subject: Merge from trunk X-Git-Tag: SQUID_3_5_0_1~75^2~2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=2c6150cc05571f098a2dc7351a4d896f0160ac0a;p=thirdparty%2Fsquid.git Merge from trunk --- 2c6150cc05571f098a2dc7351a4d896f0160ac0a diff --cc src/client_side.cc index 206b607d22,9b2a8ffd35..8e842e3b85 --- a/src/client_side.cc +++ b/src/client_side.cc @@@ -2898,276 -2893,10 +2897,276 @@@ ConnStateData::concurrentRequestQueueFi return false; } +/** + * Perform proxy_protocol_access ACL tests on the client which + * connected to PROXY protocol port to see if we trust the + * sender enough to accept their PROXY header claim. + */ +bool +ConnStateData::proxyProtocolValidateClient() +{ + if (!Config.accessList.proxyProtocol) + return proxyProtocolError("PROXY client not permitted by default ACL"); + + ACLFilledChecklist ch(Config.accessList.proxyProtocol, NULL, clientConnection->rfc931); + ch.src_addr = clientConnection->remote; + ch.my_addr = clientConnection->local; + ch.conn(this); + + if (ch.fastCheck() != ACCESS_ALLOWED) + return proxyProtocolError("PROXY client not permitted by ACLs"); + + return true; +} + +/** + * Perform cleanup on PROXY protocol errors. + * If header parsing hits a fatal error terminate the connection, + * otherwise wait for more data. + */ +bool +ConnStateData::proxyProtocolError(const char *msg) +{ + if (msg) { + // This is important to know, but maybe not so much that flooding the log is okay. +#if QUIET_PROXY_PROTOCOL + // display the first of every 32 occurances at level 1, the others at level 2. + static uint8_t hide = 0; + debugs(33, (hide++ % 32 == 0 ? DBG_IMPORTANT : 2), msg << " from " << clientConnection); +#else + debugs(33, DBG_IMPORTANT, msg << " from " << clientConnection); +#endif + mustStop(msg); + } + return false; +} + +/// magic octet prefix for PROXY protocol version 1 +static const SBuf Proxy1p0magic("PROXY ", 6); + +/// magic octet prefix for PROXY protocol version 2 +static const SBuf Proxy2p0magic("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12); + +/** + * Test the connection read buffer for PROXY protocol header. + * Version 1 and 2 header currently supported. + */ +bool +ConnStateData::parseProxyProtocolHeader() +{ + // http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt + + // detect and parse PROXY/2.0 protocol header + if (in.buf.startsWith(Proxy2p0magic)) + return parseProxy2p0(); + + // detect and parse PROXY/1.0 protocol header + if (in.buf.startsWith(Proxy1p0magic)) + return parseProxy1p0(); + + // detect and terminate other protocols + if (in.buf.length() >= Proxy2p0magic.length()) { + // PROXY/1.0 magic is shorter, so we know that + // the input does not start with any PROXY magic + return proxyProtocolError("PROXY protocol error: invalid header"); + } + + // TODO: detect short non-magic prefixes earlier to avoid + // waiting for more data which may never come + + // not enough bytes to parse yet. + return false; +} + +/// parse the PROXY/1.0 protocol header from the connection read buffer +bool +ConnStateData::parseProxy1p0() +{ + ::Parser::Tokenizer tok(in.buf); + tok.skip(Proxy1p0magic); + + // skip to first LF (assumes it is part of CRLF) + static const CharacterSet lineContent = CharacterSet::LF.complement("non-LF"); + SBuf line; + if (tok.prefix(line, lineContent, 107-Proxy1p0magic.length())) { + if (tok.skip('\n')) { + // found valid header + in.buf = tok.remaining(); + needProxyProtocolHeader_ = false; + // reset the tokenizer to work on found line only. + tok.reset(line); + } else + return false; // no LF yet + + } else // protocol error only if there are more than 107 bytes prefix header + return proxyProtocolError(in.buf.length() > 107? "PROXY/1.0 error: missing CRLF" : NULL); + + static const SBuf unknown("UNKNOWN"), tcpName("TCP"); + if (tok.skip(tcpName)) { + + // skip TCP/IP version number + static const CharacterSet tcpVersions("TCP-version","46"); + if(!tok.skipOne(tcpVersions)) + return proxyProtocolError("PROXY/1.0 error: missing TCP version"); + + // skip SP after protocol version + if (!tok.skip(' ')) + return proxyProtocolError("PROXY/1.0 error: missing SP"); + + SBuf ipa, ipb; + int64_t porta, portb; + static const CharacterSet ipChars = CharacterSet("IP Address",".:") + CharacterSet::HEXDIG; + + // parse: src-IP SP dst-IP SP src-port SP dst-port CR + // leave the LF until later. + const bool correct = tok.prefix(ipa, ipChars) && tok.skip(' ') && + tok.prefix(ipb, ipChars) && tok.skip(' ') && + tok.int64(porta) && tok.skip(' ') && + tok.int64(portb) && + tok.skip('\r'); + if (!correct) + return proxyProtocolError("PROXY/1.0 error: invalid syntax"); + + // parse IP and port strings + Ip::Address originalClient, originalDest; + + if (!originalClient.GetHostByName(ipa.c_str())) + return proxyProtocolError("PROXY/1.0 error: invalid src-IP address"); + + if (!originalDest.GetHostByName(ipb.c_str())) + return proxyProtocolError("PROXY/1.0 error: invalid dst-IP address"); + + if (porta > 0 && porta <= 0xFFFF) // max uint16_t + originalClient.port(static_cast(porta)); + else + return proxyProtocolError("PROXY/1.0 error: invalid src port"); + + if (portb > 0 && portb <= 0xFFFF) // max uint16_t + originalDest.port(static_cast(portb)); + else + return proxyProtocolError("PROXY/1.0 error: invalid dst port"); + + // we have original client and destination details now + // replace the client connection values + debugs(33, 5, "PROXY/1.0 protocol on connection " << clientConnection); + clientConnection->local = originalDest; + clientConnection->remote = originalClient; + clientConnection->flags ^= COMM_TRANSPARENT; // prevent TPROXY spoofing of this new IP. + debugs(33, 5, "PROXY/1.0 upgrade: " << clientConnection); + + // repeat fetch ensuring the new client FQDN can be logged + if (Config.onoff.log_fqdn) + fqdncache_gethostbyaddr(clientConnection->remote, FQDN_LOOKUP_IF_MISS); + + return true; + + } else if (tok.skip(unknown)) { + // found valid but unusable header + return true; + + } else + return proxyProtocolError("PROXY/1.0 error: invalid protocol family"); + + return false; +} + +/// parse the PROXY/2.0 protocol header from the connection read buffer +bool +ConnStateData::parseProxy2p0() +{ + static const SBuf::size_type prefixLen = Proxy2p0magic.length(); + if (in.buf.length() < prefixLen + 4) + return false; // need more bytes + + if ((in.buf[prefixLen] & 0xF0) != 0x20) // version == 2 is mandatory + return proxyProtocolError("PROXY/2.0 error: invalid version"); + + const char command = (in.buf[prefixLen] & 0x0F); + if ((command & 0xFE) != 0x00) // values other than 0x0-0x1 are invalid + return proxyProtocolError("PROXY/2.0 error: invalid command"); + + const char family = (in.buf[prefixLen+1] & 0xF0) >>4; + if (family > 0x3) // values other than 0x0-0x3 are invalid + return proxyProtocolError("PROXY/2.0 error: invalid family"); + + const char proto = (in.buf[prefixLen+1] & 0x0F); + if (proto > 0x2) // values other than 0x0-0x2 are invalid + return proxyProtocolError("PROXY/2.0 error: invalid protocol type"); + + const char *clen = in.buf.rawContent() + prefixLen + 2; + uint16_t len; + memcpy(&len, clen, sizeof(len)); + len = ntohs(len); + + if (in.buf.length() < prefixLen + 4 + len) + return false; // need more bytes + + in.buf.consume(prefixLen + 4); // 4 being the extra bytes + const SBuf extra = in.buf.consume(len); + needProxyProtocolHeader_ = false; // found successfully + + // LOCAL connections do nothing with the extras + if (command == 0x00/* LOCAL*/) + return true; + + union pax { + struct { /* for TCP/UDP over IPv4, len = 12 */ + struct in_addr src_addr; + struct in_addr dst_addr; + uint16_t src_port; + uint16_t dst_port; + } ipv4_addr; + struct { /* for TCP/UDP over IPv6, len = 36 */ + struct in6_addr src_addr; + struct in6_addr dst_addr; + uint16_t src_port; + uint16_t dst_port; + } ipv6_addr; +#if NOT_SUPPORTED + struct { /* for AF_UNIX sockets, len = 216 */ + uint8_t src_addr[108]; + uint8_t dst_addr[108]; + } unix_addr; +#endif + }; + + pax ipu; + memcpy(&ipu, extra.rawContent(), sizeof(pax)); + + // replace the client connection values + debugs(33, 5, "PROXY/2.0 protocol on connection " << clientConnection); + switch (family) + { + case 0x1: // IPv4 + clientConnection->local = ipu.ipv4_addr.dst_addr; + clientConnection->local.port(ntohs(ipu.ipv4_addr.dst_port)); + clientConnection->remote = ipu.ipv4_addr.src_addr; + clientConnection->remote.port(ntohs(ipu.ipv4_addr.src_port)); + clientConnection->flags ^= COMM_TRANSPARENT; // prevent TPROXY spoofing of this new IP. + break; + case 0x2: // IPv6 + clientConnection->local = ipu.ipv6_addr.dst_addr; + clientConnection->local.port(ntohs(ipu.ipv6_addr.dst_port)); + clientConnection->remote = ipu.ipv6_addr.src_addr; + clientConnection->remote.port(ntohs(ipu.ipv6_addr.src_port)); + clientConnection->flags ^= COMM_TRANSPARENT; // prevent TPROXY spoofing of this new IP. + break; + default: // do nothing + break; + } + debugs(33, 5, "PROXY/2.0 upgrade: " << clientConnection); + + // repeat fetch ensuring the new client FQDN can be logged + if (Config.onoff.log_fqdn) + fqdncache_gethostbyaddr(clientConnection->remote, FQDN_LOOKUP_IF_MISS); + + return true; +} + /** * Attempt to parse one or more requests from the input buffer. - * If a request is successfully parsed, even if the next request - * is only partially parsed, it will return TRUE. + * Returns true after completing parsing of at least one request [header]. That + * includes cases where parsing ended with an error (e.g., a huge request). */ bool ConnStateData::clientParseRequests() @@@ -3189,24 -2918,9 +3188,13 @@@ if (concurrentRequestQueueFilled()) break; + // try to parse the PROXY protocol header magic bytes + if (needProxyProtocolHeader_ && !parseProxyProtocolHeader()) + break; + Http::ProtocolVersion http_ver; - ClientSocketContext *context = parseOneRequest(http_ver); - - /* partial or incomplete request */ - if (!context) { - // TODO: why parseHttpRequest can just return parseHttpRequestAbort - // (which becomes context) but checkHeaderLimits cannot? - checkHeaderLimits(); - break; - } - - /* status -1 or 1 */ - if (context) { - debugs(33, 5, HERE << clientConnection << ": parsed a request"); + if (ClientSocketContext *context = parseOneRequest(http_ver)) { + debugs(33, 5, clientConnection << ": done parsing a request"); AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "clientLifetimeTimeout", CommTimeoutCbPtrFun(clientLifetimeTimeout, context->http)); commSetConnTimeout(clientConnection, Config.Timeout.lifetime, timeoutCall);