From 1e9a538e05c0107c54ef81d9de7cd0b27cd13309 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 20 Oct 2022 15:21:12 +0200 Subject: [PATCH] noproxy: support proxies specified using cidr notation For both IPv4 and IPv6 addresses. Now also checks IPv6 addresses "correctly" and not with string comparisons. Split out the noproxy checks and functionality into noproxy.c Added unit test 1614 to verify checking functions. Reported-by: Mathieu Carbonneaux Fixes #9773 Fixes #5745 Closes #9775 --- docs/KNOWN_BUGS | 8 -- docs/libcurl/opts/CURLOPT_NOPROXY.3 | 4 - lib/Makefile.inc | 2 + lib/noproxy.c | 212 ++++++++++++++++++++++++++++ lib/noproxy.h | 44 ++++++ lib/url.c | 82 +---------- tests/data/Makefile.inc | 2 +- tests/data/test1614 | 25 ++++ tests/unit/Makefile.inc | 5 +- tests/unit/unit1614.c | 133 +++++++++++++++++ 10 files changed, 424 insertions(+), 93 deletions(-) create mode 100644 lib/noproxy.c create mode 100644 lib/noproxy.h create mode 100644 tests/data/test1614 create mode 100644 tests/unit/unit1614.c diff --git a/docs/KNOWN_BUGS b/docs/KNOWN_BUGS index 5a1dea1b5..6cbcd51de 100644 --- a/docs/KNOWN_BUGS +++ b/docs/KNOWN_BUGS @@ -119,7 +119,6 @@ problems may have been fixed or changed somewhat since this was written. 11.9 DoH doesn't inherit all transfer options 11.10 Blocking socket operations in non-blocking API 11.11 A shared connection cache is not thread-safe - 11.12 'no_proxy' string-matches IPv6 numerical addresses 11.13 wakeup socket disconnect causes havoc 11.14 Multi perform hangs waiting for threaded resolver 11.15 CURLOPT_OPENSOCKETPAIRFUNCTION is missing @@ -931,13 +930,6 @@ problems may have been fixed or changed somewhat since this was written. See https://github.com/curl/curl/issues/4915 and lib1541.c -11.12 'no_proxy' string-matches IPv6 numerical addresses - - This has the downside that "::1" for example doesn't match "::0:1" even - though they are in fact the same address. - - See https://github.com/curl/curl/issues/5745 - 11.13 wakeup socket disconnect causes havoc waking an iPad breaks the wakeup socket pair, triggering a POLLIN event and diff --git a/docs/libcurl/opts/CURLOPT_NOPROXY.3 b/docs/libcurl/opts/CURLOPT_NOPROXY.3 index da336014f..138f57637 100644 --- a/docs/libcurl/opts/CURLOPT_NOPROXY.3 +++ b/docs/libcurl/opts/CURLOPT_NOPROXY.3 @@ -53,10 +53,6 @@ brackets: "example.com,::1,localhost" -IPv6 numerical addresses are compared as strings, so they will only match if -the representations are the same: "::1" is the same as "::0:1" but they don't -match. - The application does not have to keep the string around after setting this option. .SH "Environment variables" diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 229f42d86..b2d2e9e52 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -177,6 +177,7 @@ LIB_CFILES = \ netrc.c \ non-ascii.c \ nonblock.c \ + noproxy.c \ openldap.c \ parsedate.c \ pingpong.c \ @@ -301,6 +302,7 @@ LIB_HFILES = \ netrc.h \ non-ascii.h \ nonblock.h \ + noproxy.h \ parsedate.h \ pingpong.h \ pop3.h \ diff --git a/lib/noproxy.c b/lib/noproxy.c new file mode 100644 index 000000000..701a09e04 --- /dev/null +++ b/lib/noproxy.c @@ -0,0 +1,212 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2022, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_PROXY + +#include "inet_pton.h" +#include "strcase.h" +#include "noproxy.h" + +/* + * Curl_cidr4_match() returns TRUE if the given IPv4 address is within the + * specified CIDR address range. + */ +UNITTEST bool Curl_cidr4_match(const char *ipv4, /* 1.2.3.4 address */ + const char *network, /* 1.2.3.4 address */ + unsigned int bits) +{ + unsigned int address = 0; + unsigned int check = 0; + + if(bits > 32) + /* strange input */ + return FALSE; + + if(1 != Curl_inet_pton(AF_INET, ipv4, &address)) + return FALSE; + if(1 != Curl_inet_pton(AF_INET, network, &check)) + return FALSE; + + if(bits && (bits != 32)) { + unsigned int mask = 0xffffffff << (32 - bits); + unsigned int haddr = htonl(address); + unsigned int hcheck = htonl(check); +#if 0 + fprintf(stderr, "Host %s (%x) network %s (%x) bits %u mask %x => %x\n", + ipv4, haddr, network, hcheck, bits, mask, + (haddr ^ hcheck) & mask); +#endif + if((haddr ^ hcheck) & mask) + return FALSE; + return TRUE; + } + return (address == check); +} + +UNITTEST bool Curl_cidr6_match(const char *ipv6, + const char *network, + unsigned int bits) +{ + int bytes; + int rest; + unsigned char address[16]; + unsigned char check[16]; + + if(!bits) + bits = 128; + + bytes = bits/8; + rest = bits & 0x07; + if(1 != Curl_inet_pton(AF_INET6, ipv6, address)) + return FALSE; + if(1 != Curl_inet_pton(AF_INET6, network, check)) + return FALSE; + if((bytes > 16) || ((bytes == 16) && rest)) + return FALSE; + if(bytes && memcmp(address, check, bytes)) + return FALSE; + if(rest && !((address[bytes] ^ check[bytes]) & (0xff << (8 - rest)))) + return FALSE; + + return TRUE; +} + +enum nametype { + TYPE_HOST, + TYPE_IPV4, + TYPE_IPV6 +}; + +/**************************************************************** +* Checks if the host is in the noproxy list. returns TRUE if it matches and +* therefore the proxy should NOT be used. +****************************************************************/ +bool Curl_check_noproxy(const char *name, const char *no_proxy) +{ + /* no_proxy=domain1.dom,host.domain2.dom + * (a comma-separated list of hosts which should + * not be proxied, or an asterisk to override + * all proxy variables) + */ + if(no_proxy && no_proxy[0]) { + const char *p = no_proxy; + size_t namelen; + enum nametype type = TYPE_HOST; + char hostip[128]; + if(!strcmp("*", no_proxy)) + return TRUE; + + /* NO_PROXY was specified and it wasn't just an asterisk */ + + if(name[0] == '[') { + char *endptr; + /* IPv6 numerical address */ + endptr = strchr(name, ']'); + if(!endptr) + return FALSE; + name++; + namelen = endptr - name; + if(namelen >= sizeof(hostip)) + return FALSE; + memcpy(hostip, name, namelen); + hostip[namelen] = 0; + name = hostip; + type = TYPE_IPV6; + } + else { + unsigned int address; + if(1 == Curl_inet_pton(AF_INET, name, &address)) + type = TYPE_IPV4; + namelen = strlen(name); + } + + while(*p) { + const char *token; + size_t tokenlen = 0; + bool match = FALSE; + + /* pass blanks */ + while(*p && ISBLANK(*p)) + p++; + + token = p; + /* pass over the pattern */ + while(*p && !ISBLANK(*p) && (*p != ',')) { + p++; + tokenlen++; + } + + if(tokenlen) { + switch(type) { + case TYPE_HOST: + if(*token == '.') { + ++token; + --tokenlen; + /* tailmatch */ + match = (tokenlen <= namelen) && + strncasecompare(token, name + (namelen - tokenlen), namelen); + } + else + match = (tokenlen == namelen) && + strncasecompare(token, name, namelen); + break; + case TYPE_IPV4: + /* FALLTHROUGH */ + case TYPE_IPV6: { + const char *check = token; + char *slash = strchr(check, '/'); + unsigned int bits = 0; + char checkip[128]; + /* if the slash is part of this token, use it */ + if(slash && (slash < &check[tokenlen])) { + bits = atoi(slash + 1); + /* copy the check name to a temp buffer */ + if(tokenlen >= sizeof(checkip)) + break; + memcpy(checkip, check, tokenlen); + checkip[ slash - check ] = 0; + check = checkip; + } + if(type == TYPE_IPV6) + match = Curl_cidr6_match(name, check, bits); + else + match = Curl_cidr4_match(name, check, bits); + break; + } + } + if(match) + return TRUE; + } /* if(tokenlen) */ + while(*p == ',') + p++; + } /* while(*p) */ + } /* NO_PROXY was specified and it wasn't just an asterisk */ + + return FALSE; +} + +#endif /* CURL_DISABLE_PROXY */ + diff --git a/lib/noproxy.h b/lib/noproxy.h new file mode 100644 index 000000000..8800a2127 --- /dev/null +++ b/lib/noproxy.h @@ -0,0 +1,44 @@ +#ifndef HEADER_CURL_NOPROXY_H +#define HEADER_CURL_NOPROXY_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2022, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#ifndef CURL_DISABLE_PROXY + +#ifdef DEBUGBUILD + +UNITTEST bool Curl_cidr4_match(const char *ipv4, /* 1.2.3.4 address */ + const char *network, /* 1.2.3.4 address */ + unsigned int bits); +UNITTEST bool Curl_cidr6_match(const char *ipv6, + const char *network, + unsigned int bits); +#endif + +bool Curl_check_noproxy(const char *name, const char *no_proxy); + +#endif + +#endif /* HEADER_CURL_NOPROXY_H */ diff --git a/lib/url.c b/lib/url.c index 5ace2fdd8..a3be56bce 100644 --- a/lib/url.c +++ b/lib/url.c @@ -106,6 +106,7 @@ bool Curl_win32_idn_to_ascii(const char *in, char **out); #include "urlapi-int.h" #include "system_win32.h" #include "hsts.h" +#include "noproxy.h" /* And now for the protocols */ #include "ftp.h" @@ -2261,83 +2262,6 @@ void Curl_free_request_state(struct Curl_easy *data) #ifndef CURL_DISABLE_PROXY -/**************************************************************** -* Checks if the host is in the noproxy list. returns true if it matches -* and therefore the proxy should NOT be used. -****************************************************************/ -static bool check_noproxy(const char *name, const char *no_proxy) -{ - /* no_proxy=domain1.dom,host.domain2.dom - * (a comma-separated list of hosts which should - * not be proxied, or an asterisk to override - * all proxy variables) - */ - if(no_proxy && no_proxy[0]) { - size_t tok_start; - size_t tok_end; - const char *separator = ", "; - size_t no_proxy_len; - size_t namelen; - char *endptr; - if(strcasecompare("*", no_proxy)) { - return TRUE; - } - - /* NO_PROXY was specified and it wasn't just an asterisk */ - - no_proxy_len = strlen(no_proxy); - if(name[0] == '[') { - /* IPv6 numerical address */ - endptr = strchr(name, ']'); - if(!endptr) - return FALSE; - name++; - namelen = endptr - name; - } - else - namelen = strlen(name); - - for(tok_start = 0; tok_start < no_proxy_len; tok_start = tok_end + 1) { - while(tok_start < no_proxy_len && - strchr(separator, no_proxy[tok_start]) != NULL) { - /* Look for the beginning of the token. */ - ++tok_start; - } - - if(tok_start == no_proxy_len) - break; /* It was all trailing separator chars, no more tokens. */ - - for(tok_end = tok_start; tok_end < no_proxy_len && - strchr(separator, no_proxy[tok_end]) == NULL; ++tok_end) - /* Look for the end of the token. */ - ; - - /* To match previous behavior, where it was necessary to specify - * ".local.com" to prevent matching "notlocal.com", we will leave - * the '.' off. - */ - if(no_proxy[tok_start] == '.') - ++tok_start; - - if((tok_end - tok_start) <= namelen) { - /* Match the last part of the name to the domain we are checking. */ - const char *checkn = name + namelen - (tok_end - tok_start); - if(strncasecompare(no_proxy + tok_start, checkn, - tok_end - tok_start)) { - if((tok_end - tok_start) == namelen || *(checkn - 1) == '.') { - /* We either have an exact match, or the previous character is a . - * so it is within the same domain, so no proxy for this host. - */ - return TRUE; - } - } - } /* if((tok_end - tok_start) <= namelen) */ - } /* for(tok_start = 0; tok_start < no_proxy_len; - tok_start = tok_end + 1) */ - } /* NO_PROXY was specified and it wasn't just an asterisk */ - - return FALSE; -} #ifndef CURL_DISABLE_HTTP /**************************************************************** @@ -2706,8 +2630,8 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, } } - if(check_noproxy(conn->host.name, data->set.str[STRING_NOPROXY] ? - data->set.str[STRING_NOPROXY] : no_proxy)) { + if(Curl_check_noproxy(conn->host.name, data->set.str[STRING_NOPROXY] ? + data->set.str[STRING_NOPROXY] : no_proxy)) { Curl_safefree(proxy); Curl_safefree(socksproxy); } diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc index 8195ad7ec..b3727a6c6 100644 --- a/tests/data/Makefile.inc +++ b/tests/data/Makefile.inc @@ -202,7 +202,7 @@ test1566 test1567 test1568 test1569 test1570 \ test1590 test1591 test1592 test1593 test1594 test1595 test1596 \ \ test1600 test1601 test1602 test1603 test1604 test1605 test1606 test1607 \ -test1608 test1609 test1610 test1611 test1612 test1613 \ +test1608 test1609 test1610 test1611 test1612 test1613 test1614 \ \ test1620 test1621 \ \ diff --git a/tests/data/test1614 b/tests/data/test1614 new file mode 100644 index 000000000..4a9d54eb6 --- /dev/null +++ b/tests/data/test1614 @@ -0,0 +1,25 @@ + + + +unittest + + + +# +# Client-side + + +none + + +unittest +proxy + + +cidr comparisons + + + +0 + + diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc index f86cb7c39..831a82033 100644 --- a/tests/unit/Makefile.inc +++ b/tests/unit/Makefile.inc @@ -34,7 +34,7 @@ UNITPROGS = unit1300 unit1301 unit1302 unit1303 unit1304 unit1305 unit1307 \ unit1330 unit1394 unit1395 unit1396 unit1397 unit1398 \ unit1399 \ unit1600 unit1601 unit1602 unit1603 unit1604 unit1605 unit1606 unit1607 \ - unit1608 unit1609 unit1610 unit1611 unit1612 \ + unit1608 unit1609 unit1610 unit1611 unit1612 unit1614 \ unit1620 unit1621 \ unit1650 unit1651 unit1652 unit1653 unit1654 unit1655 \ unit1660 @@ -132,6 +132,9 @@ unit1611_CPPFLAGS = $(AM_CPPFLAGS) unit1612_SOURCES = unit1612.c $(UNITFILES) unit1612_CPPFLAGS = $(AM_CPPFLAGS) +unit1614_SOURCES = unit1614.c $(UNITFILES) +unit1614_CPPFLAGS = $(AM_CPPFLAGS) + unit1620_SOURCES = unit1620.c $(UNITFILES) unit1620_CPPFLAGS = $(AM_CPPFLAGS) diff --git a/tests/unit/unit1614.c b/tests/unit/unit1614.c new file mode 100644 index 000000000..d64f651c8 --- /dev/null +++ b/tests/unit/unit1614.c @@ -0,0 +1,133 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2022, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curlcheck.h" + +#include "noproxy.h" + +static CURLcode unit_setup(void) +{ + return CURLE_OK; +} + +static void unit_stop(void) +{ + +} + +struct check { + const char *a; + const char *n; + unsigned int bits; + bool match; +}; + +struct noproxy { + const char *a; + const char *n; + bool match; +}; + +UNITTEST_START +#ifdef DEBUGBUILD +{ + int i; + int err = 0; + struct check list4[]= { + { "192.160.0.1", "192.160.0.1", 33, FALSE}, + { "192.160.0.1", "192.160.0.1", 32, TRUE}, + { "192.160.0.1", "192.160.0.1", 0, TRUE}, + { "192.160.0.1", "192.160.0.1", 24, TRUE}, + { "192.160.0.1", "192.160.0.1", 26, TRUE}, + { "192.160.0.1", "192.160.0.1", 20, TRUE}, + { "192.160.0.1", "192.160.0.1", 18, TRUE}, + { "192.160.0.1", "192.160.0.1", 12, TRUE}, + { "192.160.0.1", "192.160.0.1", 8, TRUE}, + { "192.160.0.1", "10.0.0.1", 8, FALSE}, + { "192.160.0.1", "10.0.0.1", 32, FALSE}, + { "192.160.0.1", "10.0.0.1", 0, FALSE}, + { NULL, NULL, 0, FALSE} /* end marker */ + }; + struct check list6[]= { + { "::1", "::1", 0, TRUE}, + { "::1", "::1", 128, TRUE}, + { "::1", "0:0::1", 128, TRUE}, + { "::1", "0:0::1", 129, FALSE}, + { "fe80::ab47:4396:55c9:8474", "fe80::ab47:4396:55c9:8474", 64, TRUE}, + { NULL, NULL, 0, FALSE} /* end marker */ + }; + struct noproxy list[]= { + { "foobar", "barfoo", FALSE}, + { "foobar", "foobar", TRUE}, + { "192.168.0.1", "foobar", FALSE}, + { "192.168.0.1", "192.168.0.0/16", TRUE}, + { "192.168.0.1", "192.168.0.0/24", TRUE}, + { "192.168.0.1", "192.168.0.0/32", FALSE}, + { "192.168.0.1", "192.168.0.0", FALSE}, + { "192.168.1.1", "192.168.0.0/24", FALSE}, + { "192.168.1.1", "foo, bar, 192.168.0.0/24", FALSE}, + { "192.168.1.1", "foo, bar, 192.168.0.0/16", TRUE}, + { "[::1]", "foo, bar, 192.168.0.0/16", FALSE}, + { "[::1]", "foo, bar, ::1/64", TRUE}, + { "bar", "foo, bar, ::1/64", TRUE}, + { "BAr", "foo, bar, ::1/64", TRUE}, + { "BAr", "foo,,,,, bar, ::1/64", TRUE}, + { "www.example.com", "foo, .example.com", TRUE}, + { "www.example.com", "www2.example.com, .example.net", FALSE}, + { "example.com", ".example.com, .example.net", TRUE}, + { "nonexample.com", ".example.com, .example.net", FALSE}, + { NULL, NULL, FALSE} + }; + for(i = 0; list4[i].a; i++) { + bool match = Curl_cidr4_match(list4[i].a, list4[i].n, list4[i].bits); + if(match != list4[i].match) { + fprintf(stderr, "%s in %s/%u should %smatch\n", + list4[i].a, list4[i].n, list4[i].bits, + list4[i].match ? "": "not "); + err++; + } + } + for(i = 0; list6[i].a; i++) { + bool match = Curl_cidr6_match(list6[i].a, list6[i].n, list6[i].bits); + if(match != list6[i].match) { + fprintf(stderr, "%s in %s/%u should %smatch\n", + list6[i].a, list6[i].n, list6[i].bits, + list6[i].match ? "": "not "); + err++; + } + } + for(i = 0; list[i].a; i++) { + bool match = Curl_check_noproxy(list[i].a, list[i].n); + if(match != list[i].match) { + fprintf(stderr, "%s in %s should %smatch\n", + list[i].a, list[i].n, + list[i].match ? "": "not "); + err++; + } + } + return err; +} +#else +return 0; +#endif +UNITTEST_STOP -- 2.49.0 From 36474f1050c7f4117e3c8de6cc9217cfebfc717d Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Fri, 21 Oct 2022 19:06:25 +0000 Subject: [PATCH] noproxy: fix builds without AF_INET6 Regression from 1e9a538e05c0107c54ef81d9de7cd0b27cd13309 Reviewed-by: Daniel Stenberg Closes #9778 --- lib/noproxy.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/noproxy.c b/lib/noproxy.c index 701a09e04..0b9355a66 100644 --- a/lib/noproxy.c +++ b/lib/noproxy.c @@ -70,6 +70,7 @@ UNITTEST bool Curl_cidr6_match(const char *ipv6, const char *network, unsigned int bits) { +#ifdef ENABLE_IPV6 int bytes; int rest; unsigned char address[16]; @@ -92,6 +93,9 @@ UNITTEST bool Curl_cidr6_match(const char *ipv6, return FALSE; return TRUE; +#else + return FALSE; +#endif } enum nametype { -- 2.49.0 From 9b63dda16ec02c758f43143abcb8c5eeaecef8ae Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sat, 22 Oct 2022 23:20:26 +0000 Subject: [PATCH] noproxy: silence unused variable warnings with no ipv6 Follow-up to 36474f1050c7f4117e3c8de6cc9217cfebfc717d Reviewed-by: Daniel Stenberg Closes #9782 --- lib/noproxy.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/noproxy.c b/lib/noproxy.c index 0b9355a66..4b70a4be4 100644 --- a/lib/noproxy.c +++ b/lib/noproxy.c @@ -94,6 +94,9 @@ UNITTEST bool Curl_cidr6_match(const char *ipv6, return TRUE; #else + (void)ipv6; + (void)network; + (void)bits; return FALSE; #endif } @@ -213,4 +216,3 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy) } #endif /* CURL_DISABLE_PROXY */ - -- 2.49.0 From 86c0029047022ea150df1f1a35f184dd7315288a Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Tue, 25 Oct 2022 15:19:28 +0000 Subject: [PATCH] noproxy: include netinet/in.h for htonl() Solve the Amiga build warning by including `netinet/in.h`. `krb5.c` and `socketpair.c` are using `htonl()` too. This header is already included in those sources. Regression from 1e9a538e05c0107c54ef81d9de7cd0b27cd13309 Reviewed-by: Daniel Stenberg Closes #9787 --- lib/noproxy.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/noproxy.c b/lib/noproxy.c index 4b70a4be4..81f1e0993 100644 --- a/lib/noproxy.c +++ b/lib/noproxy.c @@ -30,6 +30,10 @@ #include "strcase.h" #include "noproxy.h" +#ifdef HAVE_NETINET_IN_H +#include +#endif + /* * Curl_cidr4_match() returns TRUE if the given IPv4 address is within the * specified CIDR address range. -- 2.49.0 From efc286b7a62af0568fdcbf3c68791c9955182128 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 27 Oct 2022 13:54:27 +0200 Subject: [PATCH] noproxy: also match with adjacent comma If the host name is an IP address and the noproxy string contained that IP address with a following comma, it would erroneously not match. Extended test 1614 to verify this combo as well. Reported-by: Henning Schild Fixes #9813 Closes #9814 --- lib/noproxy.c | 20 ++++++++++++-------- tests/data/test1614 | 2 +- tests/unit/unit1614.c | 14 ++++++++++++++ 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/lib/noproxy.c b/lib/noproxy.c index 3409dab6f..58bc69a2d 100644 --- a/lib/noproxy.c +++ b/lib/noproxy.c @@ -192,18 +192,22 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy) /* FALLTHROUGH */ case TYPE_IPV6: { const char *check = token; - char *slash = strchr(check, '/'); + char *slash; unsigned int bits = 0; char checkip[128]; + if(tokenlen >= sizeof(checkip)) + /* this cannot match */ + break; + /* copy the check name to a temp buffer */ + memcpy(checkip, check, tokenlen); + checkip[tokenlen] = 0; + check = checkip; + + slash = strchr(check, '/'); /* if the slash is part of this token, use it */ - if(slash && (slash < &check[tokenlen])) { + if(slash) { bits = atoi(slash + 1); - /* copy the check name to a temp buffer */ - if(tokenlen >= sizeof(checkip)) - break; - memcpy(checkip, check, tokenlen); - checkip[ slash - check ] = 0; - check = checkip; + *slash = 0; /* null terminate there */ } if(type == TYPE_IPV6) match = Curl_cidr6_match(name, check, bits); diff --git a/tests/data/test1614 b/tests/data/test1614 index 4a9d54eb6..73bdbb4e0 100644 --- a/tests/data/test1614 +++ b/tests/data/test1614 @@ -16,7 +16,7 @@ unittest proxy -cidr comparisons +noproxy and cidr comparisons diff --git a/tests/unit/unit1614.c b/tests/unit/unit1614.c index 60285450c..c2f563a0d 100644 --- a/tests/unit/unit1614.c +++ b/tests/unit/unit1614.c @@ -77,6 +77,20 @@ UNITTEST_START { NULL, NULL, 0, FALSE} /* end marker */ }; struct noproxy list[]= { + { "127.0.0.1", "127.0.0.1,localhost", TRUE}, + { "127.0.0.1", "127.0.0.1,localhost,", TRUE}, + { "127.0.0.1", "127.0.0.1/8,localhost,", TRUE}, + { "127.0.0.1", "127.0.0.1/28,localhost,", TRUE}, + { "127.0.0.1", "127.0.0.1/31,localhost,", TRUE}, + { "127.0.0.1", "localhost,127.0.0.1", TRUE}, + { "127.0.0.1", "localhost,127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1." + "127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1.127." + "0.0.1.127.0.0.1.127.0.0." /* 128 bytes "address" */, FALSE}, + { "127.0.0.1", "localhost,127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1." + "127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1.127." + "0.0.1.127.0.0.1.127.0.0" /* 127 bytes "address" */, FALSE}, + { "localhost", "localhost,127.0.0.1", TRUE}, + { "localhost", "127.0.0.1,localhost", TRUE}, { "foobar", "barfoo", FALSE}, { "foobar", "foobar", TRUE}, { "192.168.0.1", "foobar", FALSE}, -- 2.49.0 From d4fed2a13a81d23e73f1fb491c335a1b1d91e3fb Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Fri, 28 Oct 2022 00:07:14 +0200 Subject: [PATCH] docs: explain the noproxy CIDR notation support Follow-up to 1e9a538e05c0107c Closes #9818 --- docs/cmdline-opts/noproxy.d | 5 +++++ docs/cmdline-opts/page-footer | 7 ++++--- docs/libcurl/opts/CURLOPT_NOPROXY.3 | 5 +++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/cmdline-opts/noproxy.d b/docs/cmdline-opts/noproxy.d index c28e897eb..998f57071 100644 --- a/docs/cmdline-opts/noproxy.d +++ b/docs/cmdline-opts/noproxy.d @@ -19,3 +19,8 @@ not www.notlocal.com. Since 7.53.0, This option overrides the environment variables that disable the proxy. If there's an environment variable disabling a proxy, you can set noproxy list to \&"" to override it. + +Since 7.86.0, IP addresses specified to this option can be provided using CIDR +notation: an appended slash and number specifies the number of "network bits" +out of the address to use in the comparison. For example "192.168.0.0/16" +would match all addresses starting with "192.168". diff --git a/docs/cmdline-opts/page-footer b/docs/cmdline-opts/page-footer index c8a65be04..99459ba3d 100644 --- a/docs/cmdline-opts/page-footer +++ b/docs/cmdline-opts/page-footer @@ -37,9 +37,10 @@ accesses the target URL through the proxy. The list of host names can also be include numerical IP addresses, and IPv6 versions should then be given without enclosing brackets. -IPv6 numerical addresses are compared as strings, so they will only match if -the representations are the same: "::1" is the same as "::0:1" but they don't -match. +Since 7.86.0, IP addresses can be specified using CIDR notation: an appended +slash and number specifies the number of "network bits" out of the address to +use in the comparison. For example "192.168.0.0/16" would match all addresses +starting with "192.168". .IP "CURL_SSL_BACKEND " If curl was built with support for "MultiSSL", meaning that it has built-in support for more than one TLS backend, this environment variable can be set to diff --git a/docs/libcurl/opts/CURLOPT_NOPROXY.3 b/docs/libcurl/opts/CURLOPT_NOPROXY.3 index 138f57637..5e4c32130 100644 --- a/docs/libcurl/opts/CURLOPT_NOPROXY.3 +++ b/docs/libcurl/opts/CURLOPT_NOPROXY.3 @@ -53,6 +53,11 @@ brackets: "example.com,::1,localhost" +Since 7.86.0, IP addresses specified to this option can be provided using CIDR +notation: an appended slash and number specifies the number of "network bits" +out of the address to use in the comparison. For example "192.168.0.0/16" +would match all addresses starting with "192.168". + The application does not have to keep the string around after setting this option. .SH "Environment variables" -- 2.49.0 From b830f9ba9e94acf672cd191993ff679fa888838b Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Fri, 28 Oct 2022 10:51:49 +0200 Subject: [PATCH] noproxy: fix tail-matching Also ignore trailing dots in both host name and comparison pattern. Regression in 7.86.0 (from 1e9a538e05c0) Extended test 1614 to verify better. Reported-by: Henning Schild Fixes #9821 Closes #9822 --- lib/noproxy.c | 30 +++++++++++++++++++++++------- tests/unit/unit1614.c | 9 +++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/lib/noproxy.c b/lib/noproxy.c index 58bc69a2d..2832ae166 100644 --- a/lib/noproxy.c +++ b/lib/noproxy.c @@ -153,9 +153,14 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy) } else { unsigned int address; + namelen = strlen(name); if(1 == Curl_inet_pton(AF_INET, name, &address)) type = TYPE_IPV4; - namelen = strlen(name); + else { + /* ignore trailing dots in the host name */ + if(name[namelen - 1] == '.') + namelen--; + } } while(*p) { @@ -177,12 +182,23 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy) if(tokenlen) { switch(type) { case TYPE_HOST: - if(*token == '.') { - ++token; - --tokenlen; - /* tailmatch */ - match = (tokenlen <= namelen) && - strncasecompare(token, name + (namelen - tokenlen), namelen); + /* ignore trailing dots in the token to check */ + if(token[tokenlen - 1] == '.') + tokenlen--; + + if(tokenlen && (*token == '.')) { + /* A: example.com matches '.example.com' + B: www.example.com matches '.example.com' + C: nonexample.com DOES NOT match '.example.com' + */ + if((tokenlen - 1) == namelen) + /* case A, exact match without leading dot */ + match = strncasecompare(token + 1, name, namelen); + else if(tokenlen < namelen) + /* case B, tailmatch with leading dot */ + match = strncasecompare(token, name + (namelen - tokenlen), + tokenlen); + /* case C passes through, not a match */ } else match = (tokenlen == namelen) && diff --git a/tests/unit/unit1614.c b/tests/unit/unit1614.c index c2f563a0d..8f62b70d4 100644 --- a/tests/unit/unit1614.c +++ b/tests/unit/unit1614.c @@ -77,6 +77,15 @@ UNITTEST_START { NULL, NULL, 0, FALSE} /* end marker */ }; struct noproxy list[]= { + { "www.example.com", "localhost,.example.com,.example.de", TRUE}, + { "www.example.com.", "localhost,.example.com,.example.de", TRUE}, + { "example.com", "localhost,.example.com,.example.de", TRUE}, + { "example.com.", "localhost,.example.com,.example.de", TRUE}, + { "www.example.com", "localhost,.example.com.,.example.de", TRUE}, + { "www.example.com", "localhost,www.example.com.,.example.de", TRUE}, + { "example.com", "localhost,example.com,.example.de", TRUE}, + { "example.com.", "localhost,example.com,.example.de", TRUE}, + { "www.example.com", "localhost,example.com,.example.de", FALSE}, { "127.0.0.1", "127.0.0.1,localhost", TRUE}, { "127.0.0.1", "127.0.0.1,localhost,", TRUE}, { "127.0.0.1", "127.0.0.1/8,localhost,", TRUE}, -- 2.49.0 From b1953c1933b369b1217ef0f16053e26da63488c3 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sun, 6 Nov 2022 23:19:51 +0100 Subject: [PATCH] noproxy: tailmatch like in 7.85.0 and earlier A regfression in 7.86.0 (via 1e9a538e05c010) made the tailmatch work differently than before. This restores the logic to how it used to work: All names listed in NO_PROXY are tailmatched against the used domain name, if the lengths are identical it needs a full match. Update the docs, update test 1614. Reported-by: Stuart Henderson Fixes #9842 Closes #9858 --- docs/libcurl/opts/CURLOPT_NOPROXY.3 | 4 ---- lib/noproxy.c | 32 +++++++++++++++-------------- tests/unit/unit1614.c | 3 ++- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/docs/libcurl/opts/CURLOPT_NOPROXY.3 b/docs/libcurl/opts/CURLOPT_NOPROXY.3 index 5e4c32130..dc3cf7c10 100644 --- a/docs/libcurl/opts/CURLOPT_NOPROXY.3 +++ b/docs/libcurl/opts/CURLOPT_NOPROXY.3 @@ -40,10 +40,6 @@ list is matched as either a domain which contains the hostname, or the example.com:80, and www.example.com, but not www.notanexample.com or example.com.othertld. -If the name in the noproxy list has a leading period, it is a domain match -against the provided host name. This way ".example.com" will switch off proxy -use for both "www.example.com" as well as for "foo.example.com". - Setting the noproxy string to "" (an empty string) will explicitly enable the proxy for all host names, even if there is an environment variable set for it. diff --git a/lib/noproxy.c b/lib/noproxy.c index 2832ae166..fb856e4fa 100644 --- a/lib/noproxy.c +++ b/lib/noproxy.c @@ -187,22 +187,24 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy) tokenlen--; if(tokenlen && (*token == '.')) { - /* A: example.com matches '.example.com' - B: www.example.com matches '.example.com' - C: nonexample.com DOES NOT match '.example.com' - */ - if((tokenlen - 1) == namelen) - /* case A, exact match without leading dot */ - match = strncasecompare(token + 1, name, namelen); - else if(tokenlen < namelen) - /* case B, tailmatch with leading dot */ - match = strncasecompare(token, name + (namelen - tokenlen), - tokenlen); - /* case C passes through, not a match */ + /* ignore leading token dot as well */ + token++; + tokenlen--; } - else - match = (tokenlen == namelen) && - strncasecompare(token, name, namelen); + /* A: example.com matches 'example.com' + B: www.example.com matches 'example.com' + C: nonexample.com DOES NOT match 'example.com' + */ + if(tokenlen == namelen) + /* case A, exact match */ + match = strncasecompare(token, name, namelen); + else if(tokenlen < namelen) { + /* case B, tailmatch domain */ + match = (name[namelen - tokenlen - 1] == '.') && + strncasecompare(token, name + (namelen - tokenlen), + tokenlen); + } + /* case C passes through, not a match */ break; case TYPE_IPV4: /* FALLTHROUGH */ diff --git a/tests/unit/unit1614.c b/tests/unit/unit1614.c index 8f62b70d4..523d102bf 100644 --- a/tests/unit/unit1614.c +++ b/tests/unit/unit1614.c @@ -85,7 +85,8 @@ UNITTEST_START { "www.example.com", "localhost,www.example.com.,.example.de", TRUE}, { "example.com", "localhost,example.com,.example.de", TRUE}, { "example.com.", "localhost,example.com,.example.de", TRUE}, - { "www.example.com", "localhost,example.com,.example.de", FALSE}, + { "nexample.com", "localhost,example.com,.example.de", FALSE}, + { "www.example.com", "localhost,example.com,.example.de", TRUE}, { "127.0.0.1", "127.0.0.1,localhost", TRUE}, { "127.0.0.1", "127.0.0.1,localhost,", TRUE}, { "127.0.0.1", "127.0.0.1/8,localhost,", TRUE}, -- 2.49.0 From 60453483b5c26447df95a8a80712e809094cb2a2 Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Thu, 8 Dec 2022 23:37:32 +0100 Subject: [PATCH] noproxy: guard against empty hostnames in noproxy check When checking for a noproxy setting we need to ensure that we get a hostname passed in. If there is no hostname then there cannot be a matching noproxy rule for it by definition. Closes: #10057 Reported-by: Geeknik Labs Reviewed-by: Daniel Stenberg --- lib/noproxy.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/noproxy.c b/lib/noproxy.c index fb856e4fa..9b13fe895 100644 --- a/lib/noproxy.c +++ b/lib/noproxy.c @@ -121,6 +121,13 @@ enum nametype { ****************************************************************/ bool Curl_check_noproxy(const char *name, const char *no_proxy) { + /* + * If we don't have a hostname at all, like for example with a FILE + * transfer, we have nothing to interrogate the noproxy list with. + */ + if(!name || name[0] == '\0') + return FALSE; + /* no_proxy=domain1.dom,host.domain2.dom * (a comma-separated list of hosts which should * not be proxied, or an asterisk to override -- 2.49.0 From 7ad8a7ba9ebdedceafe8859d3bd4d22ee447648d Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 3 Jan 2023 14:58:37 +0100 Subject: [PATCH] noproxy: support for space-separated names is deprecated To be removed in July 2024. Assisted-by: Michael Osipov Fixes #10209 Closes #10215 --- lib/noproxy.c | 13 ++++++- lib/noproxy.h | 3 +- lib/url.c | 6 ++- tests/unit/unit1614.c | 91 ++++++++++++++++++++++++------------------- 5 files changed, 88 insertions(+), 44 deletions(-) diff --git a/lib/noproxy.c b/lib/noproxy.c index 6c0c486f8..f1c1ed2c6 100644 --- a/lib/noproxy.c +++ b/lib/noproxy.c @@ -119,8 +119,10 @@ enum nametype { * Checks if the host is in the noproxy list. returns TRUE if it matches and * therefore the proxy should NOT be used. ****************************************************************/ -bool Curl_check_noproxy(const char *name, const char *no_proxy) +bool Curl_check_noproxy(const char *name, const char *no_proxy, + bool *spacesep) { + *spacesep = FALSE; /* * If we don't have a hostname at all, like for example with a FILE * transfer, we have nothing to interrogate the noproxy list with. @@ -244,6 +246,15 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy) if(match) return TRUE; } /* if(tokenlen) */ + /* pass blanks after pattern */ + while(ISBLANK(*p)) + p++; + /* if not a comma! */ + if(*p && (*p != ',')) { + *spacesep = TRUE; + continue; + } + /* pass any number of commas */ while(*p == ',') p++; } /* while(*p) */ diff --git a/lib/noproxy.h b/lib/noproxy.h index b6a1a556a..a3a680772 100644 --- a/lib/noproxy.h +++ b/lib/noproxy.h @@ -37,7 +37,8 @@ UNITTEST bool Curl_cidr6_match(const char *ipv6, unsigned int bits); #endif -bool Curl_check_noproxy(const char *name, const char *no_proxy); +bool Curl_check_noproxy(const char *name, const char *no_proxy, + bool *spacesep); #endif diff --git a/lib/url.c b/lib/url.c index e531a821c..f90427f9b 100644 --- a/lib/url.c +++ b/lib/url.c @@ -2401,6 +2401,7 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, char *socksproxy = NULL; char *no_proxy = NULL; CURLcode result = CURLE_OK; + bool spacesep = FALSE; /************************************************************* * Extract the user and password from the authentication string @@ -2447,7 +2448,8 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, } if(Curl_check_noproxy(conn->host.name, data->set.str[STRING_NOPROXY] ? - data->set.str[STRING_NOPROXY] : no_proxy)) { + data->set.str[STRING_NOPROXY] : no_proxy, + &spacesep)) { Curl_safefree(proxy); Curl_safefree(socksproxy); } @@ -2456,6 +2458,8 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, /* if the host is not in the noproxy list, detect proxy. */ proxy = detect_proxy(data, conn); #endif /* CURL_DISABLE_HTTP */ + if(spacesep) + infof(data, "space-separated NOPROXY patterns are deprecated"); Curl_safefree(no_proxy); diff --git a/tests/unit/unit1614.c b/tests/unit/unit1614.c index cc685b9ce..0818ea7ed 100644 --- a/tests/unit/unit1614.c +++ b/tests/unit/unit1614.c @@ -46,6 +46,7 @@ struct noproxy { const char *a; const char *n; bool match; + bool space; /* space separated */ }; UNITTEST_START @@ -77,50 +78,52 @@ UNITTEST_START { NULL, NULL, 0, FALSE} /* end marker */ }; struct noproxy list[]= { - { "www.example.com", "localhost,.example.com,.example.de", TRUE}, - { "www.example.com.", "localhost,.example.com,.example.de", TRUE}, - { "example.com", "localhost,.example.com,.example.de", TRUE}, - { "example.com.", "localhost,.example.com,.example.de", TRUE}, - { "www.example.com", "localhost,.example.com.,.example.de", TRUE}, - { "www.example.com", "localhost,www.example.com.,.example.de", TRUE}, - { "example.com", "localhost,example.com,.example.de", TRUE}, - { "example.com.", "localhost,example.com,.example.de", TRUE}, - { "nexample.com", "localhost,example.com,.example.de", FALSE}, - { "www.example.com", "localhost,example.com,.example.de", TRUE}, - { "127.0.0.1", "127.0.0.1,localhost", TRUE}, - { "127.0.0.1", "127.0.0.1,localhost,", TRUE}, - { "127.0.0.1", "127.0.0.1/8,localhost,", TRUE}, - { "127.0.0.1", "127.0.0.1/28,localhost,", TRUE}, - { "127.0.0.1", "127.0.0.1/31,localhost,", TRUE}, - { "127.0.0.1", "localhost,127.0.0.1", TRUE}, + { "www.example.com", "localhost .example.com .example.de", TRUE, TRUE}, + { "www.example.com", "localhost,.example.com,.example.de", TRUE, FALSE}, + { "www.example.com.", "localhost,.example.com,.example.de", TRUE, FALSE}, + { "example.com", "localhost,.example.com,.example.de", TRUE, FALSE}, + { "example.com.", "localhost,.example.com,.example.de", TRUE, FALSE}, + { "www.example.com", "localhost,.example.com.,.example.de", TRUE, FALSE}, + { "www.example.com", "localhost,www.example.com.,.example.de", + TRUE, FALSE}, + { "example.com", "localhost,example.com,.example.de", TRUE, FALSE}, + { "example.com.", "localhost,example.com,.example.de", TRUE, FALSE}, + { "nexample.com", "localhost,example.com,.example.de", FALSE, FALSE}, + { "www.example.com", "localhost,example.com,.example.de", TRUE, FALSE}, + { "127.0.0.1", "127.0.0.1,localhost", TRUE, FALSE}, + { "127.0.0.1", "127.0.0.1,localhost,", TRUE, FALSE}, + { "127.0.0.1", "127.0.0.1/8,localhost,", TRUE, FALSE}, + { "127.0.0.1", "127.0.0.1/28,localhost,", TRUE, FALSE}, + { "127.0.0.1", "127.0.0.1/31,localhost,", TRUE, FALSE}, + { "127.0.0.1", "localhost,127.0.0.1", TRUE, FALSE}, { "127.0.0.1", "localhost,127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1." "127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1.127." - "0.0.1.127.0.0.1.127.0.0." /* 128 bytes "address" */, FALSE}, + "0.0.1.127.0.0.1.127.0.0." /* 128 bytes "address" */, FALSE, FALSE}, { "127.0.0.1", "localhost,127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1." "127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1.127." - "0.0.1.127.0.0.1.127.0.0" /* 127 bytes "address" */, FALSE}, - { "localhost", "localhost,127.0.0.1", TRUE}, - { "localhost", "127.0.0.1,localhost", TRUE}, - { "foobar", "barfoo", FALSE}, - { "foobar", "foobar", TRUE}, - { "192.168.0.1", "foobar", FALSE}, - { "192.168.0.1", "192.168.0.0/16", TRUE}, - { "192.168.0.1", "192.168.0.0/24", TRUE}, - { "192.168.0.1", "192.168.0.0/32", FALSE}, - { "192.168.0.1", "192.168.0.0", FALSE}, - { "192.168.1.1", "192.168.0.0/24", FALSE}, - { "192.168.1.1", "foo, bar, 192.168.0.0/24", FALSE}, - { "192.168.1.1", "foo, bar, 192.168.0.0/16", TRUE}, - { "[::1]", "foo, bar, 192.168.0.0/16", FALSE}, - { "[::1]", "foo, bar, ::1/64", TRUE}, - { "bar", "foo, bar, ::1/64", TRUE}, - { "BAr", "foo, bar, ::1/64", TRUE}, - { "BAr", "foo,,,,, bar, ::1/64", TRUE}, - { "www.example.com", "foo, .example.com", TRUE}, - { "www.example.com", "www2.example.com, .example.net", FALSE}, - { "example.com", ".example.com, .example.net", TRUE}, - { "nonexample.com", ".example.com, .example.net", FALSE}, - { NULL, NULL, FALSE} + "0.0.1.127.0.0.1.127.0.0" /* 127 bytes "address" */, FALSE, FALSE}, + { "localhost", "localhost,127.0.0.1", TRUE, FALSE}, + { "localhost", "127.0.0.1,localhost", TRUE, FALSE}, + { "foobar", "barfoo", FALSE, FALSE}, + { "foobar", "foobar", TRUE, FALSE}, + { "192.168.0.1", "foobar", FALSE, FALSE}, + { "192.168.0.1", "192.168.0.0/16", TRUE, FALSE}, + { "192.168.0.1", "192.168.0.0/24", TRUE, FALSE}, + { "192.168.0.1", "192.168.0.0/32", FALSE, FALSE}, + { "192.168.0.1", "192.168.0.0", FALSE, FALSE}, + { "192.168.1.1", "192.168.0.0/24", FALSE, FALSE}, + { "192.168.1.1", "foo, bar, 192.168.0.0/24", FALSE, FALSE}, + { "192.168.1.1", "foo, bar, 192.168.0.0/16", TRUE, FALSE}, + { "[::1]", "foo, bar, 192.168.0.0/16", FALSE, FALSE}, + { "[::1]", "foo, bar, ::1/64", TRUE, FALSE}, + { "bar", "foo, bar, ::1/64", TRUE, FALSE}, + { "BAr", "foo, bar, ::1/64", TRUE, FALSE}, + { "BAr", "foo,,,,, bar, ::1/64", TRUE, FALSE}, + { "www.example.com", "foo, .example.com", TRUE, FALSE}, + { "www.example.com", "www2.example.com, .example.net", FALSE, FALSE}, + { "example.com", ".example.com, .example.net", TRUE, FALSE}, + { "nonexample.com", ".example.com, .example.net", FALSE, FALSE}, + { NULL, NULL, FALSE, FALSE} }; for(i = 0; list4[i].a; i++) { bool match = Curl_cidr4_match(list4[i].a, list4[i].n, list4[i].bits); -- 2.49.0 From ca05e1afba7088c2f7e96ce47d2d3a249ce7a903 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 11 Apr 2023 16:24:29 +0200 Subject: [PATCH] noproxy: pointer to local array 'hostip' is stored outside scope Ref: #10929 Closes #10933 --- lib/noproxy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/noproxy.c b/lib/noproxy.c index f1c1ed2c6..2b9908d89 100644 --- a/lib/noproxy.c +++ b/lib/noproxy.c @@ -122,6 +122,7 @@ enum nametype { bool Curl_check_noproxy(const char *name, const char *no_proxy, bool *spacesep) { + char hostip[128]; *spacesep = FALSE; /* * If we don't have a hostname at all, like for example with a FILE @@ -139,7 +140,6 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy, const char *p = no_proxy; size_t namelen; enum nametype type = TYPE_HOST; - char hostip[128]; if(!strcmp("*", no_proxy)) return TRUE; -- 2.49.0 From e78913e6b5aaf36da6e5e0e68b2300d84ab85d15 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sat, 1 Jun 2024 12:25:12 +0200 Subject: [PATCH] noproxy: patterns need to be comma separated or they will not parse correctly. Mentioned in DEPRECATED since Janurary 2023 (in 7ad8a7ba9ebdedc). Closes #13789 --- docs/DEPRECATE.md | 20 +--------- lib/noproxy.c | 13 +++--- lib/noproxy.h | 4 +- lib/url.c | 7 +--- tests/unit/unit1614.c | 92 ++++++++++++++++++++----------------------- 5 files changed, 50 insertions(+), 86 deletions(-) diff --git a/docs/DEPRECATE.md b/docs/DEPRECATE.md index 0e39903e1..073bc94e3 100644 --- a/docs/DEPRECATE.md +++ b/docs/DEPRECATE.md @@ -53,3 +34,4 @@ curl removes the support for space-separated names in July 2024. - Pipelining - axTLS - PolarSSL + - space-separated `NOPROXY` patterns diff --git a/lib/noproxy.c b/lib/noproxy.c index 62299e28f..b5aec75e7 100644 --- a/lib/noproxy.c +++ b/lib/noproxy.c @@ -119,11 +119,10 @@ enum nametype { * Checks if the host is in the noproxy list. returns TRUE if it matches and * therefore the proxy should NOT be used. ****************************************************************/ -bool Curl_check_noproxy(const char *name, const char *no_proxy, - bool *spacesep) +bool Curl_check_noproxy(const char *name, const char *no_proxy) { char hostip[128]; - *spacesep = FALSE; + /* * If we don't have a hostname at all, like for example with a FILE * transfer, we have nothing to interrogate the noproxy list with. @@ -248,11 +247,9 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy, /* pass blanks after pattern */ while(ISBLANK(*p)) p++; - /* if not a comma! */ - if(*p && (*p != ',')) { - *spacesep = TRUE; - continue; - } + /* if not a comma, this ends the loop */ + if(*p != ',') + break; /* pass any number of commas */ while(*p == ',') p++; diff --git a/lib/noproxy.h b/lib/noproxy.h index f4890bbf0..71ae7eaaf 100644 --- a/lib/noproxy.h +++ b/lib/noproxy.h @@ -37,9 +37,7 @@ UNITTEST bool Curl_cidr6_match(const char *ipv6, unsigned int bits); #endif -bool Curl_check_noproxy(const char *name, const char *no_proxy, - bool *spacesep); - +bool Curl_check_noproxy(const char *name, const char *no_proxy); #endif #endif /* HEADER_CURL_NOPROXY_H */ diff --git a/lib/url.c b/lib/url.c index 7287d2d89..41e35e153 100644 --- a/lib/url.c +++ b/lib/url.c @@ -2374,7 +2374,6 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, char *socksproxy = NULL; char *no_proxy = NULL; CURLcode result = CURLE_OK; - bool spacesep = FALSE; /************************************************************* * Extract the user and password from the authentication string @@ -2421,8 +2420,7 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, } if(Curl_check_noproxy(conn->host.name, data->set.str[STRING_NOPROXY] ? - data->set.str[STRING_NOPROXY] : no_proxy, - &spacesep)) { + data->set.str[STRING_NOPROXY] : no_proxy)) { Curl_safefree(proxy); Curl_safefree(socksproxy); } @@ -2431,9 +2429,6 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, /* if the host is not in the noproxy list, detect proxy. */ proxy = detect_proxy(data, conn); #endif /* CURL_DISABLE_HTTP */ - if(spacesep) - infof(data, "space-separated NOPROXY patterns are deprecated"); - Curl_safefree(no_proxy); #ifdef USE_UNIX_SOCKETS diff --git a/tests/unit/unit1614.c b/tests/unit/unit1614.c index ef03e3b4c..b516db249 100644 --- a/tests/unit/unit1614.c +++ b/tests/unit/unit1614.c @@ -46,7 +46,6 @@ struct noproxy { const char *a; const char *n; bool match; - bool space; /* space separated */ }; UNITTEST_START @@ -78,52 +77,51 @@ UNITTEST_START { NULL, NULL, 0, FALSE} /* end marker */ }; struct noproxy list[]= { - { "www.example.com", "localhost .example.com .example.de", TRUE, TRUE}, - { "www.example.com", "localhost,.example.com,.example.de", TRUE, FALSE}, - { "www.example.com.", "localhost,.example.com,.example.de", TRUE, FALSE}, - { "example.com", "localhost,.example.com,.example.de", TRUE, FALSE}, - { "example.com.", "localhost,.example.com,.example.de", TRUE, FALSE}, - { "www.example.com", "localhost,.example.com.,.example.de", TRUE, FALSE}, - { "www.example.com", "localhost,www.example.com.,.example.de", - TRUE, FALSE}, - { "example.com", "localhost,example.com,.example.de", TRUE, FALSE}, - { "example.com.", "localhost,example.com,.example.de", TRUE, FALSE}, - { "nexample.com", "localhost,example.com,.example.de", FALSE, FALSE}, - { "www.example.com", "localhost,example.com,.example.de", TRUE, FALSE}, - { "127.0.0.1", "127.0.0.1,localhost", TRUE, FALSE}, - { "127.0.0.1", "127.0.0.1,localhost,", TRUE, FALSE}, - { "127.0.0.1", "127.0.0.1/8,localhost,", TRUE, FALSE}, - { "127.0.0.1", "127.0.0.1/28,localhost,", TRUE, FALSE}, - { "127.0.0.1", "127.0.0.1/31,localhost,", TRUE, FALSE}, - { "127.0.0.1", "localhost,127.0.0.1", TRUE, FALSE}, + { "www.example.com", "localhost .example.com .example.de", FALSE}, + { "www.example.com", "localhost,.example.com,.example.de", TRUE}, + { "www.example.com.", "localhost,.example.com,.example.de", TRUE}, + { "example.com", "localhost,.example.com,.example.de", TRUE}, + { "example.com.", "localhost,.example.com,.example.de", TRUE}, + { "www.example.com", "localhost,.example.com.,.example.de", TRUE}, + { "www.example.com", "localhost,www.example.com.,.example.de", TRUE}, + { "example.com", "localhost,example.com,.example.de", TRUE}, + { "example.com.", "localhost,example.com,.example.de", TRUE}, + { "nexample.com", "localhost,example.com,.example.de", FALSE}, + { "www.example.com", "localhost,example.com,.example.de", TRUE}, + { "127.0.0.1", "127.0.0.1,localhost", TRUE}, + { "127.0.0.1", "127.0.0.1,localhost,", TRUE}, + { "127.0.0.1", "127.0.0.1/8,localhost,", TRUE}, + { "127.0.0.1", "127.0.0.1/28,localhost,", TRUE}, + { "127.0.0.1", "127.0.0.1/31,localhost,", TRUE}, + { "127.0.0.1", "localhost,127.0.0.1", TRUE}, { "127.0.0.1", "localhost,127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1." "127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1.127." - "0.0.1.127.0.0.1.127.0.0." /* 128 bytes "address" */, FALSE, FALSE}, + "0.0.1.127.0.0.1.127.0.0." /* 128 bytes "address" */, FALSE}, { "127.0.0.1", "localhost,127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1." "127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1.127.0.0.1.127." - "0.0.1.127.0.0.1.127.0.0" /* 127 bytes "address" */, FALSE, FALSE}, - { "localhost", "localhost,127.0.0.1", TRUE, FALSE}, - { "localhost", "127.0.0.1,localhost", TRUE, FALSE}, - { "foobar", "barfoo", FALSE, FALSE}, - { "foobar", "foobar", TRUE, FALSE}, - { "192.168.0.1", "foobar", FALSE, FALSE}, - { "192.168.0.1", "192.168.0.0/16", TRUE, FALSE}, - { "192.168.0.1", "192.168.0.0/24", TRUE, FALSE}, - { "192.168.0.1", "192.168.0.0/32", FALSE, FALSE}, - { "192.168.0.1", "192.168.0.0", FALSE, FALSE}, - { "192.168.1.1", "192.168.0.0/24", FALSE, FALSE}, - { "192.168.1.1", "foo, bar, 192.168.0.0/24", FALSE, FALSE}, - { "192.168.1.1", "foo, bar, 192.168.0.0/16", TRUE, FALSE}, - { "[::1]", "foo, bar, 192.168.0.0/16", FALSE, FALSE}, - { "[::1]", "foo, bar, ::1/64", TRUE, FALSE}, - { "bar", "foo, bar, ::1/64", TRUE, FALSE}, - { "BAr", "foo, bar, ::1/64", TRUE, FALSE}, - { "BAr", "foo,,,,, bar, ::1/64", TRUE, FALSE}, - { "www.example.com", "foo, .example.com", TRUE, FALSE}, - { "www.example.com", "www2.example.com, .example.net", FALSE, FALSE}, - { "example.com", ".example.com, .example.net", TRUE, FALSE}, - { "nonexample.com", ".example.com, .example.net", FALSE, FALSE}, - { NULL, NULL, FALSE, FALSE} + "0.0.1.127.0.0.1.127.0.0" /* 127 bytes "address" */, FALSE}, + { "localhost", "localhost,127.0.0.1", TRUE}, + { "localhost", "127.0.0.1,localhost", TRUE}, + { "foobar", "barfoo", FALSE}, + { "foobar", "foobar", TRUE}, + { "192.168.0.1", "foobar", FALSE}, + { "192.168.0.1", "192.168.0.0/16", TRUE}, + { "192.168.0.1", "192.168.0.0/24", TRUE}, + { "192.168.0.1", "192.168.0.0/32", FALSE}, + { "192.168.0.1", "192.168.0.0", FALSE}, + { "192.168.1.1", "192.168.0.0/24", FALSE}, + { "192.168.1.1", "foo, bar, 192.168.0.0/24", FALSE}, + { "192.168.1.1", "foo, bar, 192.168.0.0/16", TRUE}, + { "[::1]", "foo, bar, 192.168.0.0/16", FALSE}, + { "[::1]", "foo, bar, ::1/64", TRUE}, + { "bar", "foo, bar, ::1/64", TRUE}, + { "BAr", "foo, bar, ::1/64", TRUE}, + { "BAr", "foo,,,,, bar, ::1/64", TRUE}, + { "www.example.com", "foo, .example.com", TRUE}, + { "www.example.com", "www2.example.com, .example.net", FALSE}, + { "example.com", ".example.com, .example.net", TRUE}, + { "nonexample.com", ".example.com, .example.net", FALSE}, + { NULL, NULL, FALSE} }; for(i = 0; list4[i].a; i++) { bool match = Curl_cidr4_match(list4[i].a, list4[i].n, list4[i].bits); -- 2.49.0 From 4e71f134e5aa3bd77ec645f12826e1823d06b398 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 6 Jun 2024 22:58:45 +0200 Subject: [PATCH] noproxy: test bad ipv6 net size first No need to parse anything if the size is out of range. Added some tests to this effect to test 1614. Closes #13902 --- lib/noproxy.c | 6 ++++-- tests/unit/unit1614.c | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/noproxy.c b/lib/noproxy.c index 7df40b8d7..f57a50b43 100644 --- a/lib/noproxy.c +++ b/lib/noproxy.c @@ -89,12 +89,12 @@ UNITTEST bool Curl_cidr6_match(const char *ipv6, bytes = bits/8; rest = bits & 0x07; + if((bytes > 16) || ((bytes == 16) && rest)) + return FALSE; if(1 != Curl_inet_pton(AF_INET6, ipv6, address)) return FALSE; if(1 != Curl_inet_pton(AF_INET6, network, check)) return FALSE; - if((bytes > 16) || ((bytes == 16) && rest)) - return FALSE; if(bytes && memcmp(address, check, bytes)) return FALSE; if(rest && !((address[bytes] ^ check[bytes]) & (0xff << (8 - rest)))) @@ -231,6 +231,8 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy) slash = strchr(check, '/'); /* if the slash is part of this token, use it */ if(slash) { + /* if the bits variable gets a crazy value here, that is fine as + the value will then be rejected in the cidr function */ bits = atoi(slash + 1); *slash = 0; /* null terminate there */ } diff --git a/tests/unit/unit1614.c b/tests/unit/unit1614.c index b516db249..fd6f5849c 100644 --- a/tests/unit/unit1614.c +++ b/tests/unit/unit1614.c @@ -110,10 +110,14 @@ UNITTEST_START { "192.168.0.1", "192.168.0.0/32", FALSE}, { "192.168.0.1", "192.168.0.0", FALSE}, { "192.168.1.1", "192.168.0.0/24", FALSE}, + { "192.168.1.1", "192.168.0.0/33", FALSE}, { "192.168.1.1", "foo, bar, 192.168.0.0/24", FALSE}, { "192.168.1.1", "foo, bar, 192.168.0.0/16", TRUE}, { "[::1]", "foo, bar, 192.168.0.0/16", FALSE}, { "[::1]", "foo, bar, ::1/64", TRUE}, + { "[::1]", "::1/64", TRUE}, + { "[::1]", "::1/96", TRUE}, + { "[::1]", "::1/129", FALSE}, { "bar", "foo, bar, ::1/64", TRUE}, { "BAr", "foo, bar, ::1/64", TRUE}, { "BAr", "foo,,,,, bar, ::1/64", TRUE}, -- 2.49.0