From dd9c7a652c4a94ca56be4d27db518f8dfa717a02 Mon Sep 17 00:00:00 2001 From: Ian Craggs Date: Fri, 3 Jan 2025 14:04:35 +0000 Subject: [PATCH] Add first pass of no_proxy processing #1473 --- src/MQTTProtocolOut.c | 217 +++++++++++++++--------------------------- src/Proxy.c | 201 +++++++++++++++++++++++++++++++++++++- src/Proxy.h | 8 +- 3 files changed, 282 insertions(+), 144 deletions(-) diff --git a/src/MQTTProtocolOut.c b/src/MQTTProtocolOut.c index b1d1470a..5dfde4e9 100644 --- a/src/MQTTProtocolOut.c +++ b/src/MQTTProtocolOut.c @@ -103,103 +103,6 @@ size_t MQTTProtocol_addressPort(const char* uri, int* port, const char **topic, } -/** - * Allow user or password characters to be expressed in the form of %XX, XX being the - * hexadecimal value of the character. This will avoid problems when a user code or a password - * contains a '@' or another special character ('%' included) - * @param p0 output string - * @param p1 input string - * @param basic_auth_in_len - */ -void MQTTProtocol_specialChars(char* p0, char* p1, b64_size_t *basic_auth_in_len) -{ - while (*p1 != '@') - { - if (*p1 != '%') - { - *p0++ = *p1++; - } - else if (isxdigit(*(p1 + 1)) && isxdigit(*(p1 + 2))) - { - /* next 2 characters are hexa digits */ - char hex[3]; - p1++; - hex[0] = *p1++; - hex[1] = *p1++; - hex[2] = '\0'; - *p0++ = (char)strtol(hex, 0, 16); - /* 3 input char => 1 output char */ - *basic_auth_in_len -= 2; - } - } - *p0 = 0x0; -} - - -/** - * Set the HTTP proxy for connecting - * Examples of proxy settings: - * http://your.proxy.server:8080/ - * http://user:pass@my.proxy.server:8080/ - * - * @param aClient pointer to Clients object - * @param source the proxy setting from environment or API - * @param [out] dest pointer to output proxy info - * @param [out] auth_dest pointer to output authentication material - * @param prefix expected URI prefix: http:// or https:// - * @return 0 on success, non-zero otherwise - */ -int MQTTProtocol_setHTTPProxy(Clients* aClient, char* source, char** dest, char** auth_dest, char* prefix) -{ - b64_size_t basic_auth_in_len, basic_auth_out_len; - b64_data_t *basic_auth; - char *p1; - int rc = 0; - - if (*auth_dest) - { - free(*auth_dest); - *auth_dest = NULL; - } - - if (source) - { - if ((p1 = strstr(source, prefix)) != NULL) /* skip http:// prefix, if any */ - source += strlen(prefix); - *dest = source; - if ((p1 = strchr(source, '@')) != NULL) /* find user.pass separator */ - *dest = p1 + 1; - - if (p1) - { - /* basic auth len is string between http:// and @ */ - basic_auth_in_len = (b64_size_t)(p1 - source); - if (basic_auth_in_len > 0) - { - basic_auth = (b64_data_t *)malloc(sizeof(char)*(basic_auth_in_len+1)); - if (!basic_auth) - { - rc = PAHO_MEMORY_ERROR; - goto exit; - } - MQTTProtocol_specialChars((char*)basic_auth, source, &basic_auth_in_len); - basic_auth_out_len = Base64_encodeLength(basic_auth, basic_auth_in_len) + 1; /* add 1 for trailing NULL */ - if ((*auth_dest = (char *)malloc(sizeof(char)*basic_auth_out_len)) == NULL) - { - free(basic_auth); - rc = PAHO_MEMORY_ERROR; - goto exit; - } - Base64_encode(*auth_dest, basic_auth_out_len, basic_auth, basic_auth_in_len); - free(basic_auth); - } - } - } -exit: - return rc; -} - - /** * MQTT outgoing connect processing for a client * @param address The address of the server. For TCP this is in the form @@ -241,61 +144,93 @@ int MQTTProtocol_connect(const char* address, Clients* aClient, int unixsock, in FUNC_ENTRY; aClient->good = 1; - if (aClient->httpProxy) - p0 = aClient->httpProxy; - else + if (!unixsock) { - /* Don't use the environment HTTP proxy settings by default - for backwards compatibility */ - char* use_proxy = getenv("PAHO_C_CLIENT_USE_HTTP_PROXY"); - if (use_proxy) + if (aClient->httpProxy) + p0 = aClient->httpProxy; + else /* if the proxy isn't set in the API, then we can look in the environment */ { - if (strncmp(use_proxy, "TRUE", strlen("TRUE")) == 0) - p0 = getenv("http_proxy"); + /* Don't use the environment HTTP proxy settings by default - for backwards compatibility */ + char* use_proxy = getenv("PAHO_C_CLIENT_USE_HTTP_PROXY"); + if (use_proxy) + { + if (strncmp(use_proxy, "TRUE", strlen("TRUE")) == 0) + { + char* http_proxy = getenv("http_proxy"); + if (http_proxy) + { + char* no_proxy = getenv("no_proxy"); + if (no_proxy) + { + if (Proxy_noProxy(address, no_proxy)) + p0 = http_proxy; + } + else + p0 = http_proxy; /* no no_proxy set */ + } + } + } } - } - if (p0) - { - if ((rc = MQTTProtocol_setHTTPProxy(aClient, p0, &aClient->net.http_proxy, &aClient->net.http_proxy_auth, "http://")) != 0) - goto exit; - Log(TRACE_PROTOCOL, -1, "Setting http proxy to %s", aClient->net.http_proxy); - if (aClient->net.http_proxy_auth) - Log(TRACE_PROTOCOL, -1, "Setting http proxy auth to %s", aClient->net.http_proxy_auth); + if (p0) + { + if ((rc = Proxy_setHTTPProxy(aClient, p0, &aClient->net.http_proxy, &aClient->net.http_proxy_auth, "http://")) != 0) + goto exit; + Log(TRACE_PROTOCOL, -1, "Setting http proxy to %s", aClient->net.http_proxy); + if (aClient->net.http_proxy_auth) + Log(TRACE_PROTOCOL, -1, "Setting http proxy auth to %s", aClient->net.http_proxy_auth); + } } #if defined(OPENSSL) - if (aClient->httpsProxy) - p0 = aClient->httpsProxy; - else + if (!unixsock) { - /* Don't use the environment HTTP proxy settings by default - for backwards compatibility */ - char* use_proxy = getenv("PAHO_C_CLIENT_USE_HTTP_PROXY"); - if (use_proxy) + if (aClient->httpsProxy) + p0 = aClient->httpsProxy; + else /* if the proxy isn't set in the API then we can look in the environment */ { - if (strncmp(use_proxy, "TRUE", strlen("TRUE")) == 0) - p0 = getenv("https_proxy"); - } - } - - if (p0) - { - char* prefix = NULL; - - if (memcmp(p0, "http://", 7) == 0) - prefix = "http://"; - else if (memcmp(p0, "https://", 8) == 0) - prefix = "https://"; - else - { - rc = -1; - goto exit; + /* Don't use the environment HTTP proxy settings by default - for backwards compatibility */ + char* use_proxy = getenv("PAHO_C_CLIENT_USE_HTTP_PROXY"); + if (use_proxy) + { + if (strncmp(use_proxy, "TRUE", strlen("TRUE")) == 0) + { + char* https_proxy = getenv("https_proxy"); + if (https_proxy) + { + char* no_proxy = getenv("no_proxy"); + if (no_proxy) + { + if (Proxy_noProxy(address, no_proxy)) + p0 = https_proxy; + } + else + p0 = https_proxy; /* no no_proxy set */ + } + } + } } - if ((rc = MQTTProtocol_setHTTPProxy(aClient, p0, &aClient->net.https_proxy, &aClient->net.https_proxy_auth, prefix)) != 0) - goto exit; - Log(TRACE_PROTOCOL, -1, "Setting https proxy to %s", aClient->net.https_proxy); - if (aClient->net.https_proxy_auth) - Log(TRACE_PROTOCOL, -1, "Setting https proxy auth to %s", aClient->net.https_proxy_auth); + if (p0) + { + char* prefix = NULL; + + if (memcmp(p0, "http://", 7) == 0) + prefix = "http://"; + else if (memcmp(p0, "https://", 8) == 0) + prefix = "https://"; + else + { + rc = -1; + goto exit; + } + + if ((rc = Proxy_setHTTPProxy(aClient, p0, &aClient->net.https_proxy, &aClient->net.https_proxy_auth, prefix)) != 0) + goto exit; + Log(TRACE_PROTOCOL, -1, "Setting https proxy to %s", aClient->net.https_proxy); + if (aClient->net.https_proxy_auth) + Log(TRACE_PROTOCOL, -1, "Setting https proxy auth to %s", aClient->net.https_proxy_auth); + } } if (!ssl && aClient->net.http_proxy) { diff --git a/src/Proxy.c b/src/Proxy.c index 728cb8fc..07defd85 100644 --- a/src/Proxy.c +++ b/src/Proxy.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2021 Diehl Metering. + * Copyright (c) 2009, 2024 Diehl Metering, Ian Craggs and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -38,6 +38,9 @@ #include #endif /* defined(OPENSSL) */ #include "Socket.h" +#include "Base64.h" +#include "ctype.h" + /** * Notify the IP address and port of the endpoint to proxy, and wait connection to endpoint. @@ -152,3 +155,199 @@ exit: FUNC_EXIT_RC(rc); return rc; } + + +/** + * Check the dest parameter against the no_proxy blacklist + * + * no_proxy: + * 1. Use lowercase form. + * 2. Use comma-separated hostname:port values. + * 3. IP addresses are okay, but hostnames are never resolved. + * 4. Suffixes are always matched (e.g. example.com will match test.example.com). + * 5. If top-level domains need to be matched, leading dots are accepted e.g. .com + * + * @param dest the destination hostname/ip address + * @param no_proxy the no_proxy list, probably from the environment + * @return 1 - use the proxy, 0 - don't use the proxy + */ +int Proxy_noProxy(const char* dest, char* no_proxy) +{ + char* saveptr = NULL; + char* curtok = NULL; + int port = 0; + int destport = 0; + int port_matches = 0; + size_t hostlen = 0; + size_t desthostlen = 0; + int rc = 1; + char* no_proxy_list = NULL; + + printf("dest: %s\n", dest); + printf("no_proxy list: %s\n", no_proxy); + if ((no_proxy_list = MQTTStrdup(no_proxy)) == NULL) + { + rc = PAHO_MEMORY_ERROR; + goto exit; + } + + curtok = strtok_r(no_proxy_list, ",", &saveptr); + while (curtok) + { + char* host = curtok; + int matched = 0; + int pos = 1; + const char* topic; + + printf("no_proxy to match against: %s\n", host); + if (curtok == NULL) + break; + if (host[0] == '.') + host++; + + hostlen = MQTTProtocol_addressPort(host, &port, &topic, -99); + + desthostlen = MQTTProtocol_addressPort(dest, &destport, &topic, -99); + if (dest[desthostlen] == '/') + desthostlen--; + + /* check if port matches */ + if (port == -99) + port_matches = 1; /* no_proxy port absence matches any */ + else if (destport != -99) + { + if (destport == port) + port_matches = 1; + } + if (memcmp(host, "*", 1) == 0) /* * matches anything */ + { + /* match any host or address */ + if (port_matches == 1) + { + rc = 0; + break; + } + } + /* now see if .host matches the end of the dest string */ + /* match backwards from the end */ + while (host[hostlen - pos] == dest[desthostlen - pos]) + { + if (pos == hostlen) /* reached the beginning of the no_proxy definition */ + { + if (pos == desthostlen || dest[desthostlen - pos - 1] == '.') + matched = 1; + break; + } + else if (pos == desthostlen) /* reached the beginning of the destination string */ + break; + pos++; + } + if (matched) + { + rc = 0; + break; + } + curtok = strtok_r(NULL, ",", &saveptr); + } + free(no_proxy_list); +exit: + printf("matched %d\n", !rc); + return rc; +} + + +/** + * Allow user or password characters to be expressed in the form of %XX, XX being the + * hexadecimal value of the character. This will avoid problems when a user code or a password + * contains a '@' or another special character ('%' included) + * @param p0 output string + * @param p1 input string + * @param basic_auth_in_len + */ +void Proxy_specialChars(char* p0, char* p1, b64_size_t *basic_auth_in_len) +{ + while (*p1 != '@') + { + if (*p1 != '%') + { + *p0++ = *p1++; + } + else if (isxdigit(*(p1 + 1)) && isxdigit(*(p1 + 2))) + { + /* next 2 characters are hexa digits */ + char hex[3]; + p1++; + hex[0] = *p1++; + hex[1] = *p1++; + hex[2] = '\0'; + *p0++ = (char)strtol(hex, 0, 16); + /* 3 input char => 1 output char */ + *basic_auth_in_len -= 2; + } + } + *p0 = 0x0; +} + + +/** + * Set the HTTP proxy for connecting + * Examples of proxy settings: + * http://your.proxy.server:8080/ + * http://user:pass@my.proxy.server:8080/ + * + * @param aClient pointer to Clients object + * @param source the proxy setting from environment or API + * @param [out] dest pointer to output proxy info + * @param [out] auth_dest pointer to output authentication material + * @param prefix expected URI prefix: http:// or https:// + * @return 0 on success, non-zero otherwise + */ +int Proxy_setHTTPProxy(Clients* aClient, char* source, char** dest, char** auth_dest, char* prefix) +{ + b64_size_t basic_auth_in_len, basic_auth_out_len; + b64_data_t *basic_auth; + char *p1; + int rc = 0; + + if (*auth_dest) + { + free(*auth_dest); + *auth_dest = NULL; + } + + if (source) + { + if ((p1 = strstr(source, prefix)) != NULL) /* skip http:// prefix, if any */ + source += strlen(prefix); + *dest = source; + if ((p1 = strchr(source, '@')) != NULL) /* find user.pass separator */ + *dest = p1 + 1; + + if (p1) + { + /* basic auth len is string between http:// and @ */ + basic_auth_in_len = (b64_size_t)(p1 - source); + if (basic_auth_in_len > 0) + { + basic_auth = (b64_data_t *)malloc(sizeof(char)*(basic_auth_in_len+1)); + if (!basic_auth) + { + rc = PAHO_MEMORY_ERROR; + goto exit; + } + Proxy_specialChars((char*)basic_auth, source, &basic_auth_in_len); + basic_auth_out_len = Base64_encodeLength(basic_auth, basic_auth_in_len) + 1; /* add 1 for trailing NULL */ + if ((*auth_dest = (char *)malloc(sizeof(char)*basic_auth_out_len)) == NULL) + { + free(basic_auth); + rc = PAHO_MEMORY_ERROR; + goto exit; + } + Base64_encode(*auth_dest, basic_auth_out_len, basic_auth, basic_auth_in_len); + free(basic_auth); + } + } + } +exit: + return rc; +} \ No newline at end of file diff --git a/src/Proxy.h b/src/Proxy.h index 67cf5df1..116c240f 100644 --- a/src/Proxy.h +++ b/src/Proxy.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2021 Diehl Metering. + * Copyright (c) 2009, 2024 Diehl Metering, Ian Craggs and others * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 @@ -20,6 +20,10 @@ #include "Clients.h" /* Notify the IP address and port of the endpoint to proxy, and wait connection to endpoint */ -int Proxy_connect(networkHandles *net, int ssl, const char *hostname ); +int Proxy_connect(networkHandles *net, int ssl, const char *hostname); + +int Proxy_noProxy(const char* dest, char* no_proxy); + +int Proxy_setHTTPProxy(Clients* aClient, char* source, char** dest, char** auth_dest, char* prefix); #endif /* PROXY_H */