77
88#include <aws/common/command_line_parser.h>
99#include <aws/common/condition_variable.h>
10+ #include <aws/common/error.h>
1011#include <aws/common/hash_table.h>
1112#include <aws/common/log_channel.h>
1213#include <aws/common/log_formatter.h>
2223#include <aws/io/stream.h>
2324#include <aws/io/tls_channel_handler.h>
2425#include <aws/io/uri.h>
26+ #include <aws/io/socks5.h>
27+ #include <aws/io/socks5_channel_handler.h>
2528
2629#include <inttypes.h>
30+ #include <stdbool.h>
31+ #include <stdlib.h>
32+ #include <string.h>
2733
2834#ifdef _MSC_VER
2935# pragma warning(disable : 4996) /* Disable warnings about fopen() being insecure */
3339
3440#define ELASTICURL_VERSION "0.2.0"
3541
42+ struct socks5_proxy_settings {
43+ char * host ;
44+ char * username ;
45+ char * password ;
46+ uint16_t port ;
47+ bool resolve_host_with_proxy ;
48+ };
49+
50+ static void s_socks5_proxy_settings_clean_up (
51+ struct socks5_proxy_settings * settings ,
52+ struct aws_allocator * allocator ) {
53+ if (!settings ) {
54+ return ;
55+ }
56+ if (settings -> host ) {
57+ aws_mem_release (allocator , settings -> host );
58+ }
59+ if (settings -> username ) {
60+ aws_mem_release (allocator , settings -> username );
61+ }
62+ if (settings -> password ) {
63+ aws_mem_release (allocator , settings -> password );
64+ }
65+ AWS_ZERO_STRUCT (* settings );
66+ }
67+
68+ static int s_socks5_proxy_settings_init_from_uri (
69+ struct socks5_proxy_settings * settings ,
70+ struct aws_allocator * allocator ,
71+ const char * proxy_uri ) {
72+
73+ if (!settings || !allocator || !proxy_uri ) {
74+ return aws_raise_error (AWS_ERROR_INVALID_ARGUMENT );
75+ }
76+
77+ s_socks5_proxy_settings_clean_up (settings , allocator );
78+
79+ struct aws_byte_cursor uri_cursor = aws_byte_cursor_from_c_str (proxy_uri );
80+ struct aws_uri uri ;
81+ AWS_ZERO_STRUCT (uri );
82+
83+ if (aws_uri_init_parse (& uri , allocator , & uri_cursor )) {
84+ fprintf (stderr , "Failed to parse proxy URI \"%s\": %s\n" , proxy_uri , aws_error_debug_str (aws_last_error ()));
85+ goto on_error ;
86+ }
87+
88+ const struct aws_byte_cursor * scheme = aws_uri_scheme (& uri );
89+ if (!scheme || !scheme -> len ) {
90+ fprintf (stderr , "Proxy URI \"%s\" must include scheme socks5h://\n" , proxy_uri );
91+ goto on_error ;
92+ }
93+
94+ if (aws_byte_cursor_eq_c_str_ignore_case (scheme , "socks5h" )) {
95+ settings -> resolve_host_with_proxy = true;
96+ } else if (aws_byte_cursor_eq_c_str_ignore_case (scheme , "socks5" )) {
97+ settings -> resolve_host_with_proxy = false;
98+ } else {
99+ fprintf (stderr , "Unsupported proxy scheme in \"%s\". Expected socks5h://\n" , proxy_uri );
100+ goto on_error ;
101+ }
102+
103+ const struct aws_byte_cursor * host = aws_uri_host_name (& uri );
104+ if (!host || host -> len == 0 ) {
105+ fprintf (stderr , "Proxy URI \"%s\" must include a host\n" , proxy_uri );
106+ goto on_error ;
107+ }
108+
109+ settings -> host = aws_mem_calloc (allocator , host -> len + 1 , sizeof (char ));
110+ if (!settings -> host ) {
111+ fprintf (stderr , "Failed to allocate memory for proxy host\n" );
112+ goto on_error ;
113+ }
114+ memcpy (settings -> host , host -> ptr , host -> len );
115+ settings -> host [host -> len ] = '\0' ;
116+
117+ uint32_t parsed_port = aws_uri_port (& uri );
118+ if (parsed_port == 0 ) {
119+ parsed_port = 1080 ;
120+ }
121+ if (parsed_port > UINT16_MAX ) {
122+ fprintf (stderr , "Proxy port %" PRIu32 " exceeds uint16_t range\n" , parsed_port );
123+ goto on_error ;
124+ }
125+ settings -> port = (uint16_t )parsed_port ;
126+
127+ if (uri .user .len > 0 ) {
128+ settings -> username = aws_mem_calloc (allocator , uri .user .len + 1 , sizeof (char ));
129+ if (!settings -> username ) {
130+ fprintf (stderr , "Failed to allocate memory for proxy username\n" );
131+ goto on_error ;
132+ }
133+ memcpy (settings -> username , uri .user .ptr , uri .user .len );
134+ settings -> username [uri .user .len ] = '\0' ;
135+ }
136+
137+ if (uri .password .len > 0 ) {
138+ settings -> password = aws_mem_calloc (allocator , uri .password .len + 1 , sizeof (char ));
139+ if (!settings -> password ) {
140+ fprintf (stderr , "Failed to allocate memory for proxy password\n" );
141+ goto on_error ;
142+ }
143+ memcpy (settings -> password , uri .password .ptr , uri .password .len );
144+ settings -> password [uri .password .len ] = '\0' ;
145+ }
146+
147+ aws_uri_clean_up (& uri );
148+ return AWS_OP_SUCCESS ;
149+
150+ on_error :
151+ aws_uri_clean_up (& uri );
152+ s_socks5_proxy_settings_clean_up (settings , allocator );
153+ return AWS_OP_ERR ;
154+ }
155+
36156struct elasticurl_ctx {
37157 struct aws_allocator * allocator ;
38158 const char * verb ;
@@ -64,6 +184,8 @@ struct elasticurl_ctx {
64184 enum aws_log_level log_level ;
65185 enum aws_http_version required_http_version ;
66186 bool exchange_completed ;
187+ struct socks5_proxy_settings proxy ;
188+ bool use_proxy ;
67189};
68190
69191static void s_usage (int exit_code ) {
@@ -76,6 +198,7 @@ static void s_usage(int exit_code) {
76198 fprintf (stderr , " --cert FILE: path to a PEM encoded certificate to use with mTLS\n" );
77199 fprintf (stderr , " --key FILE: Path to a PEM encoded private key that matches cert.\n" );
78200 fprintf (stderr , " --connect-timeout INT: time in milliseconds to wait for a connection.\n" );
201+ fprintf (stderr , " --proxy URL: SOCKS5 proxy URI (socks5h://... for proxy DNS, socks5://... for local DNS)\n" );
79202 fprintf (stderr , " -H, --header LINE: line to send as a header in format [header-key]: [header-value]\n" );
80203 fprintf (stderr , " -d, --data STRING: Data to POST or PUT\n" );
81204 fprintf (stderr , " --data-file FILE: File to read from file and POST or PUT\n" );
@@ -107,6 +230,7 @@ static struct aws_cli_option s_long_options[] = {
107230 {"cert" , AWS_CLI_OPTIONS_REQUIRED_ARGUMENT , NULL , 'c' },
108231 {"key" , AWS_CLI_OPTIONS_REQUIRED_ARGUMENT , NULL , 'e' },
109232 {"connect-timeout" , AWS_CLI_OPTIONS_REQUIRED_ARGUMENT , NULL , 'f' },
233+ {"proxy" , AWS_CLI_OPTIONS_REQUIRED_ARGUMENT , NULL , 'x' },
110234 {"header" , AWS_CLI_OPTIONS_REQUIRED_ARGUMENT , NULL , 'H' },
111235 {"data" , AWS_CLI_OPTIONS_REQUIRED_ARGUMENT , NULL , 'd' },
112236 {"data-file" , AWS_CLI_OPTIONS_REQUIRED_ARGUMENT , NULL , 'g' },
@@ -162,7 +286,7 @@ static void s_parse_options(int argc, char **argv, struct elasticurl_ctx *ctx) {
162286 while (true) {
163287 int option_index = 0 ;
164288 int c =
165- aws_cli_getopt_long (argc , argv , "a:b:c:e:f:H:d:g:j:l:m:M:GPHiko:t:v:VwWh " , s_long_options , & option_index );
289+ aws_cli_getopt_long (argc , argv , "a:b:c:e:f:H:d:g:j:l:m:M:GPHiko:t:v:VwWhx: " , s_long_options , & option_index );
166290 if (c == -1 ) {
167291 break ;
168292 }
@@ -186,6 +310,12 @@ static void s_parse_options(int argc, char **argv, struct elasticurl_ctx *ctx) {
186310 case 'f' :
187311 ctx -> connect_timeout = atoi (aws_cli_optarg );
188312 break ;
313+ case 'x' :
314+ if (s_socks5_proxy_settings_init_from_uri (& ctx -> proxy , ctx -> allocator , aws_cli_optarg )) {
315+ s_usage (1 );
316+ }
317+ ctx -> use_proxy = true;
318+ break ;
189319 case 'H' :
190320 if (ctx -> header_line_count >= sizeof (ctx -> header_lines ) / sizeof (const char * )) {
191321 fprintf (stderr , "currently only 10 header lines are supported.\n" );
@@ -618,6 +748,9 @@ int main(int argc, char **argv) {
618748 struct aws_tls_connection_options tls_connection_options ;
619749 AWS_ZERO_STRUCT (tls_connection_options );
620750 struct aws_tls_connection_options * tls_options = NULL ;
751+ bool socks5_options_valid = false;
752+ struct aws_socks5_proxy_options socks5_options ;
753+ AWS_ZERO_STRUCT (socks5_options );
621754
622755 if (use_tls ) {
623756 if (app_ctx .cert && app_ctx .key ) {
@@ -710,6 +843,40 @@ int main(int argc, char **argv) {
710843 .keep_alive_interval_sec = 0 ,
711844 };
712845
846+ if (app_ctx .use_proxy ) {
847+ if (!app_ctx .proxy .host ) {
848+ fprintf (stderr , "Proxy URI was requested but no host was parsed.\n" );
849+ exit (1 );
850+ }
851+
852+ struct aws_byte_cursor proxy_host = aws_byte_cursor_from_c_str (app_ctx .proxy .host );
853+ if (aws_socks5_proxy_options_init (& socks5_options , allocator , proxy_host , app_ctx .proxy .port ) != AWS_OP_SUCCESS ) {
854+ fprintf (
855+ stderr ,
856+ "Failed to initialize SOCKS5 proxy options: %s\n" ,
857+ aws_error_debug_str (aws_last_error ()));
858+ exit (1 );
859+ }
860+ aws_socks5_proxy_options_set_host_resolution_mode (
861+ & socks5_options ,
862+ app_ctx .proxy .resolve_host_with_proxy ? AWS_SOCKS5_HOST_RESOLUTION_PROXY
863+ : AWS_SOCKS5_HOST_RESOLUTION_CLIENT );
864+
865+ if (app_ctx .proxy .username && app_ctx .proxy .password ) {
866+ struct aws_byte_cursor username = aws_byte_cursor_from_c_str (app_ctx .proxy .username );
867+ struct aws_byte_cursor password = aws_byte_cursor_from_c_str (app_ctx .proxy .password );
868+ if (aws_socks5_proxy_options_set_auth (& socks5_options , allocator , username , password ) != AWS_OP_SUCCESS ) {
869+ fprintf (
870+ stderr ,
871+ "Failed to set SOCKS5 auth: %s\n" ,
872+ aws_error_debug_str (aws_last_error ()));
873+ exit (1 );
874+ }
875+ }
876+
877+ socks5_options_valid = true;
878+ }
879+
713880 struct aws_http_client_connection_options http_client_options = {
714881 .self_size = sizeof (struct aws_http_client_connection_options ),
715882 .socket_options = & socket_options ,
@@ -719,6 +886,7 @@ int main(int argc, char **argv) {
719886 .bootstrap = bootstrap ,
720887 .initial_window_size = SIZE_MAX ,
721888 .tls_options = tls_options ,
889+ .socks5_proxy_options = socks5_options_valid ? & socks5_options : NULL ,
722890 .user_data = & app_ctx ,
723891 .on_setup = s_on_client_connection_setup ,
724892 .on_shutdown = s_on_client_connection_shutdown ,
@@ -732,6 +900,10 @@ int main(int argc, char **argv) {
732900 aws_condition_variable_wait_pred (& app_ctx .c_var , & app_ctx .mutex , s_completion_predicate , & app_ctx );
733901 aws_mutex_unlock (& app_ctx .mutex );
734902
903+ if (socks5_options_valid ) {
904+ aws_socks5_proxy_options_clean_up (& socks5_options );
905+ }
906+
735907 aws_client_bootstrap_release (bootstrap );
736908 aws_host_resolver_release (resolver );
737909 aws_event_loop_group_release (el_group );
@@ -748,6 +920,7 @@ int main(int argc, char **argv) {
748920 aws_logger_clean_up (& logger );
749921 }
750922
923+ s_socks5_proxy_settings_clean_up (& app_ctx .proxy , app_ctx .allocator );
751924 aws_uri_clean_up (& app_ctx .uri );
752925
753926 aws_http_message_destroy (app_ctx .request );
0 commit comments