Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,6 @@ if (NOT BYO_CRYPTO AND BUILD_TESTING)
if (NOT CMAKE_CROSSCOMPILING)
add_subdirectory(bin/elasticurl)
add_subdirectory(bin/h2benchmark)
add_subdirectory(bin/http_client_app)
endif()
endif()
175 changes: 174 additions & 1 deletion bin/elasticurl/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <aws/common/command_line_parser.h>
#include <aws/common/condition_variable.h>
#include <aws/common/error.h>
#include <aws/common/hash_table.h>
#include <aws/common/log_channel.h>
#include <aws/common/log_formatter.h>
Expand All @@ -22,8 +23,13 @@
#include <aws/io/stream.h>
#include <aws/io/tls_channel_handler.h>
#include <aws/io/uri.h>
#include <aws/io/socks5.h>
#include <aws/io/socks5_channel_handler.h>

#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

#ifdef _MSC_VER
# pragma warning(disable : 4996) /* Disable warnings about fopen() being insecure */
Expand All @@ -33,6 +39,120 @@

#define ELASTICURL_VERSION "0.2.0"

struct socks5_proxy_settings {
char *host;
char *username;
char *password;
uint16_t port;
bool resolve_host_with_proxy;
};

static void s_socks5_proxy_settings_clean_up(
struct socks5_proxy_settings *settings,
struct aws_allocator *allocator) {
if (!settings) {
return;
}
if (settings->host) {
aws_mem_release(allocator, settings->host);
}
if (settings->username) {
aws_mem_release(allocator, settings->username);
}
if (settings->password) {
aws_mem_release(allocator, settings->password);
}
AWS_ZERO_STRUCT(*settings);
}

static int s_socks5_proxy_settings_init_from_uri(
struct socks5_proxy_settings *settings,
struct aws_allocator *allocator,
const char *proxy_uri) {

if (!settings || !allocator || !proxy_uri) {
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
}

s_socks5_proxy_settings_clean_up(settings, allocator);

struct aws_byte_cursor uri_cursor = aws_byte_cursor_from_c_str(proxy_uri);
struct aws_uri uri;
AWS_ZERO_STRUCT(uri);

if (aws_uri_init_parse(&uri, allocator, &uri_cursor)) {
fprintf(stderr, "Failed to parse proxy URI \"%s\": %s\n", proxy_uri, aws_error_debug_str(aws_last_error()));
goto on_error;
}

const struct aws_byte_cursor *scheme = aws_uri_scheme(&uri);
if (!scheme || !scheme->len) {
fprintf(stderr, "Proxy URI \"%s\" must include scheme socks5h://\n", proxy_uri);
goto on_error;
}

if (aws_byte_cursor_eq_c_str_ignore_case(scheme, "socks5h")) {
settings->resolve_host_with_proxy = true;
} else if (aws_byte_cursor_eq_c_str_ignore_case(scheme, "socks5")) {
settings->resolve_host_with_proxy = false;
} else {
fprintf(stderr, "Unsupported proxy scheme in \"%s\". Expected socks5h://\n", proxy_uri);
goto on_error;
}

const struct aws_byte_cursor *host = aws_uri_host_name(&uri);
if (!host || host->len == 0) {
fprintf(stderr, "Proxy URI \"%s\" must include a host\n", proxy_uri);
goto on_error;
}

settings->host = aws_mem_calloc(allocator, host->len + 1, sizeof(char));
if (!settings->host) {
fprintf(stderr, "Failed to allocate memory for proxy host\n");
goto on_error;
}
memcpy(settings->host, host->ptr, host->len);
settings->host[host->len] = '\0';

uint32_t parsed_port = aws_uri_port(&uri);
if (parsed_port == 0) {
parsed_port = 1080;
}
if (parsed_port > UINT16_MAX) {
fprintf(stderr, "Proxy port %" PRIu32 " exceeds uint16_t range\n", parsed_port);
goto on_error;
}
settings->port = (uint16_t)parsed_port;

if (uri.user.len > 0) {
settings->username = aws_mem_calloc(allocator, uri.user.len + 1, sizeof(char));
if (!settings->username) {
fprintf(stderr, "Failed to allocate memory for proxy username\n");
goto on_error;
}
memcpy(settings->username, uri.user.ptr, uri.user.len);
settings->username[uri.user.len] = '\0';
}

if (uri.password.len > 0) {
settings->password = aws_mem_calloc(allocator, uri.password.len + 1, sizeof(char));
if (!settings->password) {
fprintf(stderr, "Failed to allocate memory for proxy password\n");
goto on_error;
}
memcpy(settings->password, uri.password.ptr, uri.password.len);
settings->password[uri.password.len] = '\0';
}

aws_uri_clean_up(&uri);
return AWS_OP_SUCCESS;

on_error:
aws_uri_clean_up(&uri);
s_socks5_proxy_settings_clean_up(settings, allocator);
return AWS_OP_ERR;
}

struct elasticurl_ctx {
struct aws_allocator *allocator;
const char *verb;
Expand Down Expand Up @@ -64,6 +184,8 @@ struct elasticurl_ctx {
enum aws_log_level log_level;
enum aws_http_version required_http_version;
bool exchange_completed;
struct socks5_proxy_settings proxy;
bool use_proxy;
};

static void s_usage(int exit_code) {
Expand All @@ -76,6 +198,7 @@ static void s_usage(int exit_code) {
fprintf(stderr, " --cert FILE: path to a PEM encoded certificate to use with mTLS\n");
fprintf(stderr, " --key FILE: Path to a PEM encoded private key that matches cert.\n");
fprintf(stderr, " --connect-timeout INT: time in milliseconds to wait for a connection.\n");
fprintf(stderr, " --proxy URL: SOCKS5 proxy URI (socks5h://... for proxy DNS, socks5://... for local DNS)\n");
fprintf(stderr, " -H, --header LINE: line to send as a header in format [header-key]: [header-value]\n");
fprintf(stderr, " -d, --data STRING: Data to POST or PUT\n");
fprintf(stderr, " --data-file FILE: File to read from file and POST or PUT\n");
Expand Down Expand Up @@ -107,6 +230,7 @@ static struct aws_cli_option s_long_options[] = {
{"cert", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'c'},
{"key", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'e'},
{"connect-timeout", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'f'},
{"proxy", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'x'},
{"header", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'H'},
{"data", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'd'},
{"data-file", AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, NULL, 'g'},
Expand Down Expand Up @@ -162,7 +286,7 @@ static void s_parse_options(int argc, char **argv, struct elasticurl_ctx *ctx) {
while (true) {
int option_index = 0;
int c =
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);
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);
if (c == -1) {
break;
}
Expand All @@ -186,6 +310,12 @@ static void s_parse_options(int argc, char **argv, struct elasticurl_ctx *ctx) {
case 'f':
ctx->connect_timeout = atoi(aws_cli_optarg);
break;
case 'x':
if (s_socks5_proxy_settings_init_from_uri(&ctx->proxy, ctx->allocator, aws_cli_optarg)) {
s_usage(1);
}
ctx->use_proxy = true;
break;
case 'H':
if (ctx->header_line_count >= sizeof(ctx->header_lines) / sizeof(const char *)) {
fprintf(stderr, "currently only 10 header lines are supported.\n");
Expand Down Expand Up @@ -618,6 +748,9 @@ int main(int argc, char **argv) {
struct aws_tls_connection_options tls_connection_options;
AWS_ZERO_STRUCT(tls_connection_options);
struct aws_tls_connection_options *tls_options = NULL;
bool socks5_options_valid = false;
struct aws_socks5_proxy_options socks5_options;
AWS_ZERO_STRUCT(socks5_options);

if (use_tls) {
if (app_ctx.cert && app_ctx.key) {
Expand Down Expand Up @@ -710,6 +843,40 @@ int main(int argc, char **argv) {
.keep_alive_interval_sec = 0,
};

if (app_ctx.use_proxy) {
if (!app_ctx.proxy.host) {
fprintf(stderr, "Proxy URI was requested but no host was parsed.\n");
exit(1);
}

struct aws_byte_cursor proxy_host = aws_byte_cursor_from_c_str(app_ctx.proxy.host);
if (aws_socks5_proxy_options_init(&socks5_options, allocator, proxy_host, app_ctx.proxy.port) != AWS_OP_SUCCESS) {
fprintf(
stderr,
"Failed to initialize SOCKS5 proxy options: %s\n",
aws_error_debug_str(aws_last_error()));
exit(1);
}
aws_socks5_proxy_options_set_host_resolution_mode(
&socks5_options,
app_ctx.proxy.resolve_host_with_proxy ? AWS_SOCKS5_HOST_RESOLUTION_PROXY
: AWS_SOCKS5_HOST_RESOLUTION_CLIENT);

if (app_ctx.proxy.username && app_ctx.proxy.password) {
struct aws_byte_cursor username = aws_byte_cursor_from_c_str(app_ctx.proxy.username);
struct aws_byte_cursor password = aws_byte_cursor_from_c_str(app_ctx.proxy.password);
if (aws_socks5_proxy_options_set_auth(&socks5_options, allocator, username, password) != AWS_OP_SUCCESS) {
fprintf(
stderr,
"Failed to set SOCKS5 auth: %s\n",
aws_error_debug_str(aws_last_error()));
exit(1);
}
}

socks5_options_valid = true;
}

struct aws_http_client_connection_options http_client_options = {
.self_size = sizeof(struct aws_http_client_connection_options),
.socket_options = &socket_options,
Expand All @@ -719,6 +886,7 @@ int main(int argc, char **argv) {
.bootstrap = bootstrap,
.initial_window_size = SIZE_MAX,
.tls_options = tls_options,
.socks5_proxy_options = socks5_options_valid ? &socks5_options : NULL,
.user_data = &app_ctx,
.on_setup = s_on_client_connection_setup,
.on_shutdown = s_on_client_connection_shutdown,
Expand All @@ -732,6 +900,10 @@ int main(int argc, char **argv) {
aws_condition_variable_wait_pred(&app_ctx.c_var, &app_ctx.mutex, s_completion_predicate, &app_ctx);
aws_mutex_unlock(&app_ctx.mutex);

if (socks5_options_valid) {
aws_socks5_proxy_options_clean_up(&socks5_options);
}

aws_client_bootstrap_release(bootstrap);
aws_host_resolver_release(resolver);
aws_event_loop_group_release(el_group);
Expand All @@ -748,6 +920,7 @@ int main(int argc, char **argv) {
aws_logger_clean_up(&logger);
}

s_socks5_proxy_settings_clean_up(&app_ctx.proxy, app_ctx.allocator);
aws_uri_clean_up(&app_ctx.uri);

aws_http_message_destroy(app_ctx.request);
Expand Down
28 changes: 28 additions & 0 deletions bin/http_client_app/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
project(http_client_c_socks5_app C)

list(APPEND CMAKE_MODULE_PATH "${CMAKE_PREFIX_PATH}/lib/cmake")

file(GLOB HTTP_CLIENT_APP_SRC
"*.c"
)

set(HTTP_CLIENT_APP_PROJECT_NAME http_client_app)
add_executable(${HTTP_CLIENT_APP_PROJECT_NAME} ${HTTP_CLIENT_APP_SRC})
aws_set_common_properties(${HTTP_CLIENT_APP_PROJECT_NAME})

target_include_directories(${HTTP_CLIENT_APP_PROJECT_NAME} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)

target_link_libraries(${HTTP_CLIENT_APP_PROJECT_NAME} PRIVATE aws-c-http)

if (BUILD_SHARED_LIBS AND NOT WIN32)
message(INFO " http client app will be built with shared libs, but you may need to set LD_LIBRARY_PATH=${CMAKE_INSTALL_PREFIX}/lib to run the application")
endif()

install(TARGETS ${HTTP_CLIENT_APP_PROJECT_NAME}
EXPORT ${HTTP_CLIENT_APP_PROJECT_NAME}-targets
COMPONENT Runtime
RUNTIME
DESTINATION bin
COMPONENT Runtime)
Loading