Home | History | Annotate | Download | only in client_channel
      1 /*
      2  *
      3  * Copyright 2016 gRPC authors.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  *
     17  */
     18 
     19 #include <grpc/support/port_platform.h>
     20 
     21 #include "src/core/ext/filters/client_channel/http_proxy.h"
     22 
     23 #include <stdbool.h>
     24 #include <string.h>
     25 
     26 #include <grpc/support/alloc.h>
     27 #include <grpc/support/log.h>
     28 #include <grpc/support/string_util.h>
     29 
     30 #include "src/core/ext/filters/client_channel/http_connect_handshaker.h"
     31 #include "src/core/ext/filters/client_channel/proxy_mapper_registry.h"
     32 #include "src/core/ext/filters/client_channel/uri_parser.h"
     33 #include "src/core/lib/channel/channel_args.h"
     34 #include "src/core/lib/gpr/env.h"
     35 #include "src/core/lib/gpr/host_port.h"
     36 #include "src/core/lib/gpr/string.h"
     37 #include "src/core/lib/slice/b64.h"
     38 
     39 /**
     40  * Parses the 'https_proxy' env var (fallback on 'http_proxy') and returns the
     41  * proxy hostname to resolve or nullptr on error. Also sets 'user_cred' to user
     42  * credentials if present in the 'http_proxy' env var, otherwise leaves it
     43  * unchanged. It is caller's responsibility to gpr_free user_cred.
     44  */
     45 static char* get_http_proxy_server(char** user_cred) {
     46   GPR_ASSERT(user_cred != nullptr);
     47   char* proxy_name = nullptr;
     48   char** authority_strs = nullptr;
     49   size_t authority_nstrs;
     50   /* Prefer using 'https_proxy'. Fallback on 'http_proxy' if it is not set. The
     51    * fallback behavior can be removed if there's a demand for it.
     52    */
     53   char* uri_str = gpr_getenv("https_proxy");
     54   if (uri_str == nullptr) uri_str = gpr_getenv("http_proxy");
     55   if (uri_str == nullptr) return nullptr;
     56   grpc_uri* uri = grpc_uri_parse(uri_str, false /* suppress_errors */);
     57   if (uri == nullptr || uri->authority == nullptr) {
     58     gpr_log(GPR_ERROR, "cannot parse value of 'http_proxy' env var");
     59     goto done;
     60   }
     61   if (strcmp(uri->scheme, "http") != 0) {
     62     gpr_log(GPR_ERROR, "'%s' scheme not supported in proxy URI", uri->scheme);
     63     goto done;
     64   }
     65   /* Split on '@' to separate user credentials from host */
     66   gpr_string_split(uri->authority, "@", &authority_strs, &authority_nstrs);
     67   GPR_ASSERT(authority_nstrs != 0); /* should have at least 1 string */
     68   if (authority_nstrs == 1) {
     69     /* User cred not present in authority */
     70     proxy_name = authority_strs[0];
     71   } else if (authority_nstrs == 2) {
     72     /* User cred found */
     73     *user_cred = authority_strs[0];
     74     proxy_name = authority_strs[1];
     75     gpr_log(GPR_DEBUG, "userinfo found in proxy URI");
     76   } else {
     77     /* Bad authority */
     78     for (size_t i = 0; i < authority_nstrs; i++) {
     79       gpr_free(authority_strs[i]);
     80     }
     81     proxy_name = nullptr;
     82   }
     83   gpr_free(authority_strs);
     84 done:
     85   gpr_free(uri_str);
     86   grpc_uri_destroy(uri);
     87   return proxy_name;
     88 }
     89 
     90 /**
     91  * Checks the value of GRPC_ARG_ENABLE_HTTP_PROXY to determine if http_proxy
     92  * should be used.
     93  */
     94 bool http_proxy_enabled(const grpc_channel_args* args) {
     95   const grpc_arg* arg =
     96       grpc_channel_args_find(args, GRPC_ARG_ENABLE_HTTP_PROXY);
     97   return grpc_channel_arg_get_bool(arg, true);
     98 }
     99 
    100 static bool proxy_mapper_map_name(grpc_proxy_mapper* mapper,
    101                                   const char* server_uri,
    102                                   const grpc_channel_args* args,
    103                                   char** name_to_resolve,
    104                                   grpc_channel_args** new_args) {
    105   if (!http_proxy_enabled(args)) {
    106     return false;
    107   }
    108   char* user_cred = nullptr;
    109   *name_to_resolve = get_http_proxy_server(&user_cred);
    110   if (*name_to_resolve == nullptr) return false;
    111   char* no_proxy_str = nullptr;
    112   grpc_uri* uri = grpc_uri_parse(server_uri, false /* suppress_errors */);
    113   if (uri == nullptr || uri->path[0] == '\0') {
    114     gpr_log(GPR_ERROR,
    115             "'http_proxy' environment variable set, but cannot "
    116             "parse server URI '%s' -- not using proxy",
    117             server_uri);
    118     goto no_use_proxy;
    119   }
    120   if (strcmp(uri->scheme, "unix") == 0) {
    121     gpr_log(GPR_INFO, "not using proxy for Unix domain socket '%s'",
    122             server_uri);
    123     goto no_use_proxy;
    124   }
    125   no_proxy_str = gpr_getenv("no_proxy");
    126   if (no_proxy_str != nullptr) {
    127     static const char* NO_PROXY_SEPARATOR = ",";
    128     bool use_proxy = true;
    129     char* server_host;
    130     char* server_port;
    131     if (!gpr_split_host_port(uri->path[0] == '/' ? uri->path + 1 : uri->path,
    132                              &server_host, &server_port)) {
    133       gpr_log(GPR_INFO,
    134               "unable to split host and port, not checking no_proxy list for "
    135               "host '%s'",
    136               server_uri);
    137       gpr_free(no_proxy_str);
    138     } else {
    139       size_t uri_len = strlen(server_host);
    140       char** no_proxy_hosts;
    141       size_t num_no_proxy_hosts;
    142       gpr_string_split(no_proxy_str, NO_PROXY_SEPARATOR, &no_proxy_hosts,
    143                        &num_no_proxy_hosts);
    144       for (size_t i = 0; i < num_no_proxy_hosts; i++) {
    145         char* no_proxy_entry = no_proxy_hosts[i];
    146         size_t no_proxy_len = strlen(no_proxy_entry);
    147         if (no_proxy_len <= uri_len &&
    148             gpr_stricmp(no_proxy_entry, &server_host[uri_len - no_proxy_len]) ==
    149                 0) {
    150           gpr_log(GPR_INFO, "not using proxy for host in no_proxy list '%s'",
    151                   server_uri);
    152           use_proxy = false;
    153           break;
    154         }
    155       }
    156       for (size_t i = 0; i < num_no_proxy_hosts; i++) {
    157         gpr_free(no_proxy_hosts[i]);
    158       }
    159       gpr_free(no_proxy_hosts);
    160       gpr_free(server_host);
    161       gpr_free(server_port);
    162       gpr_free(no_proxy_str);
    163       if (!use_proxy) goto no_use_proxy;
    164     }
    165   }
    166   grpc_arg args_to_add[2];
    167   args_to_add[0] = grpc_channel_arg_string_create(
    168       (char*)GRPC_ARG_HTTP_CONNECT_SERVER,
    169       uri->path[0] == '/' ? uri->path + 1 : uri->path);
    170   if (user_cred != nullptr) {
    171     /* Use base64 encoding for user credentials as stated in RFC 7617 */
    172     char* encoded_user_cred =
    173         grpc_base64_encode(user_cred, strlen(user_cred), 0, 0);
    174     char* header;
    175     gpr_asprintf(&header, "Proxy-Authorization:Basic %s", encoded_user_cred);
    176     gpr_free(encoded_user_cred);
    177     args_to_add[1] = grpc_channel_arg_string_create(
    178         (char*)GRPC_ARG_HTTP_CONNECT_HEADERS, header);
    179     *new_args = grpc_channel_args_copy_and_add(args, args_to_add, 2);
    180     gpr_free(header);
    181   } else {
    182     *new_args = grpc_channel_args_copy_and_add(args, args_to_add, 1);
    183   }
    184   grpc_uri_destroy(uri);
    185   gpr_free(user_cred);
    186   return true;
    187 no_use_proxy:
    188   if (uri != nullptr) grpc_uri_destroy(uri);
    189   gpr_free(*name_to_resolve);
    190   *name_to_resolve = nullptr;
    191   gpr_free(user_cred);
    192   return false;
    193 }
    194 
    195 static bool proxy_mapper_map_address(grpc_proxy_mapper* mapper,
    196                                      const grpc_resolved_address* address,
    197                                      const grpc_channel_args* args,
    198                                      grpc_resolved_address** new_address,
    199                                      grpc_channel_args** new_args) {
    200   return false;
    201 }
    202 
    203 static void proxy_mapper_destroy(grpc_proxy_mapper* mapper) {}
    204 
    205 static const grpc_proxy_mapper_vtable proxy_mapper_vtable = {
    206     proxy_mapper_map_name, proxy_mapper_map_address, proxy_mapper_destroy};
    207 
    208 static grpc_proxy_mapper proxy_mapper = {&proxy_mapper_vtable};
    209 
    210 void grpc_register_http_proxy_mapper() {
    211   grpc_proxy_mapper_register(true /* at_start */, &proxy_mapper);
    212 }
    213