/* * * Copyright 2016 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include #include "src/core/ext/filters/client_channel/http_proxy.h" #include #include #include #include #include #include "src/core/ext/filters/client_channel/http_connect_handshaker.h" #include "src/core/ext/filters/client_channel/proxy_mapper_registry.h" #include "src/core/lib/channel/channel_args.h" #include "src/core/lib/gpr/env.h" #include "src/core/lib/gpr/string.h" #include "src/core/lib/gprpp/host_port.h" #include "src/core/lib/slice/b64.h" #include "src/core/lib/uri/uri_parser.h" namespace grpc_core { namespace { /** * Parses the 'https_proxy' env var (fallback on 'http_proxy') and returns the * proxy hostname to resolve or nullptr on error. Also sets 'user_cred' to user * credentials if present in the 'http_proxy' env var, otherwise leaves it * unchanged. It is caller's responsibility to gpr_free user_cred. */ char* GetHttpProxyServer(char** user_cred) { GPR_ASSERT(user_cred != nullptr); grpc_uri* uri = nullptr; char* proxy_name = nullptr; char** authority_strs = nullptr; size_t authority_nstrs; /* Prefer using 'grpc_proxy'. Fallback on 'http_proxy' if it is not set. * Also prefer using 'https_proxy' with fallback on 'http_proxy'. The * fallback behavior can be removed if there's a demand for it. */ char* uri_str = gpr_getenv("grpc_proxy"); if (uri_str == nullptr) uri_str = gpr_getenv("https_proxy"); if (uri_str == nullptr) uri_str = gpr_getenv("http_proxy"); if (uri_str == nullptr) return nullptr; // an emtpy value means "don't use proxy" if (uri_str[0] == '\0') goto done; uri = grpc_uri_parse(uri_str, false /* suppress_errors */); if (uri == nullptr || uri->authority == nullptr) { gpr_log(GPR_ERROR, "cannot parse value of 'http_proxy' env var"); goto done; } if (strcmp(uri->scheme, "http") != 0) { gpr_log(GPR_ERROR, "'%s' scheme not supported in proxy URI", uri->scheme); goto done; } /* Split on '@' to separate user credentials from host */ gpr_string_split(uri->authority, "@", &authority_strs, &authority_nstrs); GPR_ASSERT(authority_nstrs != 0); /* should have at least 1 string */ if (authority_nstrs == 1) { /* User cred not present in authority */ proxy_name = authority_strs[0]; } else if (authority_nstrs == 2) { /* User cred found */ *user_cred = authority_strs[0]; proxy_name = authority_strs[1]; gpr_log(GPR_DEBUG, "userinfo found in proxy URI"); } else { /* Bad authority */ for (size_t i = 0; i < authority_nstrs; i++) { gpr_free(authority_strs[i]); } proxy_name = nullptr; } gpr_free(authority_strs); done: gpr_free(uri_str); grpc_uri_destroy(uri); return proxy_name; } class HttpProxyMapper : public ProxyMapperInterface { public: bool MapName(const char* server_uri, const grpc_channel_args* args, char** name_to_resolve, grpc_channel_args** new_args) override { if (!grpc_channel_args_find_bool(args, GRPC_ARG_ENABLE_HTTP_PROXY, true)) { return false; } char* user_cred = nullptr; *name_to_resolve = GetHttpProxyServer(&user_cred); if (*name_to_resolve == nullptr) return false; char* no_proxy_str = nullptr; grpc_uri* uri = grpc_uri_parse(server_uri, false /* suppress_errors */); if (uri == nullptr || uri->path[0] == '\0') { gpr_log(GPR_ERROR, "'http_proxy' environment variable set, but cannot " "parse server URI '%s' -- not using proxy", server_uri); goto no_use_proxy; } if (strcmp(uri->scheme, "unix") == 0) { gpr_log(GPR_INFO, "not using proxy for Unix domain socket '%s'", server_uri); goto no_use_proxy; } /* Prefer using 'no_grpc_proxy'. Fallback on 'no_proxy' if it is not set. */ no_proxy_str = gpr_getenv("no_grpc_proxy"); if (no_proxy_str == nullptr) no_proxy_str = gpr_getenv("no_proxy"); if (no_proxy_str != nullptr) { static const char* NO_PROXY_SEPARATOR = ","; bool use_proxy = true; grpc_core::UniquePtr server_host; grpc_core::UniquePtr server_port; if (!grpc_core::SplitHostPort( uri->path[0] == '/' ? uri->path + 1 : uri->path, &server_host, &server_port)) { gpr_log(GPR_INFO, "unable to split host and port, not checking no_proxy list for " "host '%s'", server_uri); gpr_free(no_proxy_str); } else { size_t uri_len = strlen(server_host.get()); char** no_proxy_hosts; size_t num_no_proxy_hosts; gpr_string_split(no_proxy_str, NO_PROXY_SEPARATOR, &no_proxy_hosts, &num_no_proxy_hosts); for (size_t i = 0; i < num_no_proxy_hosts; i++) { char* no_proxy_entry = no_proxy_hosts[i]; size_t no_proxy_len = strlen(no_proxy_entry); if (no_proxy_len <= uri_len && gpr_stricmp(no_proxy_entry, &(server_host.get()[uri_len - no_proxy_len])) == 0) { gpr_log(GPR_INFO, "not using proxy for host in no_proxy list '%s'", server_uri); use_proxy = false; break; } } for (size_t i = 0; i < num_no_proxy_hosts; i++) { gpr_free(no_proxy_hosts[i]); } gpr_free(no_proxy_hosts); gpr_free(no_proxy_str); if (!use_proxy) goto no_use_proxy; } } grpc_arg args_to_add[2]; args_to_add[0] = grpc_channel_arg_string_create( (char*)GRPC_ARG_HTTP_CONNECT_SERVER, uri->path[0] == '/' ? uri->path + 1 : uri->path); if (user_cred != nullptr) { /* Use base64 encoding for user credentials as stated in RFC 7617 */ char* encoded_user_cred = grpc_base64_encode(user_cred, strlen(user_cred), 0, 0); char* header; gpr_asprintf(&header, "Proxy-Authorization:Basic %s", encoded_user_cred); gpr_free(encoded_user_cred); args_to_add[1] = grpc_channel_arg_string_create( (char*)GRPC_ARG_HTTP_CONNECT_HEADERS, header); *new_args = grpc_channel_args_copy_and_add(args, args_to_add, 2); gpr_free(header); } else { *new_args = grpc_channel_args_copy_and_add(args, args_to_add, 1); } grpc_uri_destroy(uri); gpr_free(user_cred); return true; no_use_proxy: if (uri != nullptr) grpc_uri_destroy(uri); gpr_free(*name_to_resolve); *name_to_resolve = nullptr; gpr_free(user_cred); return false; } bool MapAddress(const grpc_resolved_address& /*address*/, const grpc_channel_args* /*args*/, grpc_resolved_address** /*new_address*/, grpc_channel_args** /*new_args*/) override { return false; } }; } // namespace void RegisterHttpProxyMapper() { ProxyMapperRegistry::Register( true /* at_start */, std::unique_ptr(new HttpProxyMapper())); } } // namespace grpc_core