diff --git a/include/aws/http/connection.h b/include/aws/http/connection.h index e02a8e31a..3901ee5cd 100644 --- a/include/aws/http/connection.h +++ b/include/aws/http/connection.h @@ -121,54 +121,6 @@ struct aws_http_connection_monitoring_options { uint32_t allowable_throughput_failure_interval_seconds; }; -/** - * Supported proxy authentication modes - */ -enum aws_http_proxy_authentication_type { - AWS_HPAT_NONE = 0, - AWS_HPAT_BASIC, -}; - -/** - * Options for http proxy server usage - */ -struct aws_http_proxy_options { - - /** - * Proxy host to connect to, in lieu of actual target - */ - struct aws_byte_cursor host; - - /** - * Port to make the proxy connection to - */ - uint16_t port; - - /** - * Optional. - * TLS configuration for the Local <-> Proxy connection - * Must be distinct from the the TLS options in the parent aws_http_connection_options struct - */ - const struct aws_tls_connection_options *tls_options; - - /** - * What type of proxy authentication to use, if any - */ - enum aws_http_proxy_authentication_type auth_type; - - /** - * Optional - * User name to use for authentication, basic only - */ - struct aws_byte_cursor auth_username; - - /** - * Optional - * Password to use for authentication, basic only - */ - struct aws_byte_cursor auth_password; -}; - /** * Options specific to HTTP/1.x connections. * Initialize with AWS_HTTP1_CONNECTION_OPTIONS_INIT to set default values. @@ -482,6 +434,12 @@ enum aws_http_version aws_http_connection_get_version(const struct aws_http_conn AWS_HTTP_API struct aws_channel *aws_http_connection_get_channel(struct aws_http_connection *connection); +/** + * Checks http proxy options for correctness + */ +AWS_HTTP_API +int aws_http_options_validate_proxy_configuration(const struct aws_http_client_connection_options *options); + /** * Send a SETTINGS frame (HTTP/2 only). * SETTINGS will be applied locally when SETTINGS ACK is received from peer. diff --git a/include/aws/http/http.h b/include/aws/http/http.h index 5dc3be7ab..e6dfd90d9 100644 --- a/include/aws/http/http.h +++ b/include/aws/http/http.h @@ -36,7 +36,7 @@ enum aws_http_errors { AWS_ERROR_HTTP_CONNECTION_MANAGER_INVALID_STATE_FOR_ACQUIRE, AWS_ERROR_HTTP_CONNECTION_MANAGER_VENDED_CONNECTION_UNDERFLOW, AWS_ERROR_HTTP_SERVER_CLOSED, - AWS_ERROR_HTTP_PROXY_TLS_CONNECT_FAILED, + AWS_ERROR_HTTP_PROXY_CONNECT_FAILED, AWS_ERROR_HTTP_CONNECTION_MANAGER_SHUTTING_DOWN, AWS_ERROR_HTTP_CHANNEL_THROUGHPUT_FAILURE, AWS_ERROR_HTTP_PROTOCOL_ERROR, @@ -46,6 +46,9 @@ enum aws_http_errors { AWS_ERROR_HTTP_RST_STREAM_SENT, AWS_ERROR_HTTP_STREAM_NOT_ACTIVATED, AWS_ERROR_HTTP_STREAM_HAS_COMPLETED, + AWS_ERROR_HTTP_PROXY_STRATEGY_NTLM_CHALLENGE_TOKEN_MISSING, + AWS_ERROR_HTTP_PROXY_STRATEGY_TOKEN_RETRIEVAL_FAILURE, + AWS_ERROR_HTTP_PROXY_CONNECT_FAILED_RETRYABLE, AWS_ERROR_HTTP_END_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_HTTP_PACKAGE_ID) }; @@ -79,6 +82,7 @@ enum aws_http_log_subject { AWS_LS_HTTP_CONNECTION_MANAGER, AWS_LS_HTTP_WEBSOCKET, AWS_LS_HTTP_WEBSOCKET_SETUP, + AWS_LS_HTTP_PROXY_NEGOTIATION, }; enum aws_http_version { diff --git a/include/aws/http/private/proxy_impl.h b/include/aws/http/private/proxy_impl.h index 8a7864f90..352a2f390 100644 --- a/include/aws/http/private/proxy_impl.h +++ b/include/aws/http/private/proxy_impl.h @@ -9,11 +9,20 @@ #include #include +#include +#include +#include +struct aws_http_connection_manager_options; struct aws_http_message; struct aws_channel_slot; struct aws_string; struct aws_tls_connection_options; +struct aws_http_proxy_negotiator; +struct aws_http_proxy_strategy; +struct aws_http_proxy_strategy_tunneling_sequence_options; +struct aws_http_proxy_strategy_tunneling_kerberos_options; +struct aws_http_proxy_strategy_tunneling_ntlm_options; /* * (Successful) State transitions for proxy connections @@ -37,17 +46,15 @@ struct aws_http_proxy_config { struct aws_allocator *allocator; + enum aws_http_proxy_connection_type connection_type; + struct aws_byte_buf host; uint16_t port; struct aws_tls_connection_options *tls_options; - enum aws_http_proxy_authentication_type auth_type; - - struct aws_byte_buf auth_username; - - struct aws_byte_buf auth_password; + struct aws_http_proxy_strategy *proxy_strategy; }; /* @@ -61,12 +68,20 @@ struct aws_http_proxy_config { struct aws_http_proxy_user_data { struct aws_allocator *allocator; + /* + * dynamic proxy connection resolution state + */ enum aws_proxy_bootstrap_state state; int error_code; + enum aws_http_status_code connect_status_code; struct aws_http_connection *connection; struct aws_http_message *connect_request; struct aws_http_stream *connect_stream; + struct aws_http_proxy_negotiator *proxy_negotiator; + /* + * Cached original connect options + */ struct aws_string *original_host; uint16_t original_port; aws_http_on_client_connection_setup_fn *original_on_setup; @@ -74,6 +89,10 @@ struct aws_http_proxy_user_data { void *original_user_data; struct aws_tls_connection_options *tls_options; + struct aws_client_bootstrap *bootstrap; + struct aws_socket_options socket_options; + bool manual_window_management; + size_t initial_window_size; struct aws_http_proxy_config *proxy_config; }; @@ -103,18 +122,81 @@ int aws_http_rewrite_uri_for_proxy_request( AWS_HTTP_API void aws_http_proxy_system_set_vtable(struct aws_http_proxy_system_vtable *vtable); +/** + * Checks if tunneling proxy negotiation should continue to try and connect + * @param proxy_negotiator negotiator to query + * @return true if another connect request should be attempted, false otherwise + */ +AWS_HTTP_API +enum aws_http_proxy_negotiation_retry_directive aws_http_proxy_negotiator_get_retry_directive( + struct aws_http_proxy_negotiator *proxy_negotiator); + +/** + * Constructor for a tunnel-only proxy strategy that applies no changes to outbound CONNECT requests. Intended to be + * the first link in an adaptive sequence for a tunneling proxy: first try a basic CONNECT, then based on the response, + * later links are allowed to make attempts. + * + * @param allocator memory allocator to use + * @return a new proxy strategy if successfully constructed, otherwise NULL + */ +AWS_HTTP_API +struct aws_http_proxy_strategy *aws_http_proxy_strategy_new_tunneling_one_time_identity( + struct aws_allocator *allocator); + +/** + * Constructor for a forwarding-only proxy strategy that does nothing. Exists so that all proxy logic uses a + * strategy. + * + * @param allocator memory allocator to use + * @return a new proxy strategy if successfully constructed, otherwise NULL + */ +AWS_HTTP_API +struct aws_http_proxy_strategy *aws_http_proxy_strategy_new_forwarding_identity(struct aws_allocator *allocator); + +/** + * Constructor for a tunneling proxy strategy that contains a set of sub-strategies which are tried + * sequentially in order. Each strategy has the choice to either proceed on a fresh connection or + * reuse the current one. + * + * @param allocator memory allocator to use + * @param config sequence configuration options + * @return a new proxy strategy if successfully constructed, otherwise NULL + */ AWS_HTTP_API -struct aws_http_proxy_config *aws_http_proxy_config_new( +struct aws_http_proxy_strategy *aws_http_proxy_strategy_new_tunneling_sequence( struct aws_allocator *allocator, - const struct aws_http_proxy_options *options); + struct aws_http_proxy_strategy_tunneling_sequence_options *config); +/** + * A constructor for a proxy strategy that performs kerberos authentication by adding the appropriate + * header and header value to CONNECT requests. + * + * Currently only supports synchronous fetch of kerberos token values. + * + * @param allocator memory allocator to use + * @param config kerberos authentication configuration info + * @return a new proxy strategy if successfully constructed, otherwise NULL + */ AWS_HTTP_API -void aws_http_proxy_config_destroy(struct aws_http_proxy_config *config); +struct aws_http_proxy_strategy *aws_http_proxy_strategy_new_tunneling_kerberos( + struct aws_allocator *allocator, + struct aws_http_proxy_strategy_tunneling_kerberos_options *config); +/** + * Constructor for an NTLM proxy strategy. Because ntlm is a challenge-response authentication protocol, this + * strategy will only succeed in a chain in a non-leading position. The strategy extracts the challenge from the + * proxy's response to a previous CONNECT request in the chain. + * + * Currently only supports synchronous fetch of token values. + * + * @param allocator memory allocator to use + * @param config configuration options for the strategy + * @return a new proxy strategy if successfully constructed, otherwise NULL + */ AWS_HTTP_API -void aws_http_proxy_options_init_from_config( - struct aws_http_proxy_options *options, - const struct aws_http_proxy_config *config); +struct aws_http_proxy_strategy *aws_http_proxy_strategy_new_tunneling_ntlm( + struct aws_allocator *allocator, + struct aws_http_proxy_strategy_tunneling_ntlm_options *config); AWS_EXTERN_C_END diff --git a/include/aws/http/proxy.h b/include/aws/http/proxy.h new file mode 100644 index 000000000..97dfb9a0a --- /dev/null +++ b/include/aws/http/proxy.h @@ -0,0 +1,493 @@ +#ifndef AWS_PROXY_H +#define AWS_PROXY_H + +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include +#include +#include +#include + +struct aws_http_client_connection_options; +struct aws_http_connection_manager_options; + +struct aws_http_message; +struct aws_http_header; + +struct aws_http_proxy_config; +struct aws_http_proxy_negotiator; +struct aws_http_proxy_strategy; + +struct aws_socket_channel_bootstrap_options; + +/** + * @Deprecated - Supported proxy authentication modes. Superceded by proxy strategy. + */ +enum aws_http_proxy_authentication_type { + AWS_HPAT_NONE = 0, + AWS_HPAT_BASIC, +}; + +/** + * Supported proxy connection types + */ +enum aws_http_proxy_connection_type { + /** + * Deprecated, but 0-valued for backwards compatibility + * + * If tls options are provided (for the main connection) then treat the proxy as a tunneling proxy + * If tls options are not provided (for the main connection), then treat the proxy as a forwarding proxy + */ + AWS_HPCT_HTTP_LEGACY = 0, + + /** + * Use the proxy to forward http requests. Attempting to use both this mode and TLS on the tunnel destination + * is a configuration error. + */ + AWS_HPCT_HTTP_FORWARD, + + /** + * Use the proxy to establish a connection to a remote endpoint via a CONNECT request through the proxy. + * Works for both plaintext and tls connections. + */ + AWS_HPCT_HTTP_TUNNEL, +}; + +struct aws_http_proxy_strategy; + +/** + * Options for http proxy server usage + */ +struct aws_http_proxy_options { + + /** + * Type of proxy connection to make + */ + enum aws_http_proxy_connection_type connection_type; + + /** + * Proxy host to connect to + */ + struct aws_byte_cursor host; + + /** + * Port to make the proxy connection to + */ + uint16_t port; + + /** + * Optional. + * TLS configuration for the Local <-> Proxy connection + * Must be distinct from the the TLS options in the parent aws_http_connection_options struct + */ + const struct aws_tls_connection_options *tls_options; + + /** + * Optional + * Advanced option that allows the user to create a custom strategy that gives low-level control of + * certain logical flows within the proxy logic. + * + * For tunneling proxies it allows custom retry and adaptive negotiation of CONNECT requests. + * For forwarding proxies it allows custom request transformations. + */ + struct aws_http_proxy_strategy *proxy_strategy; + + /** + * @Deprecated - What type of proxy authentication to use, if any. + * Replaced by instantiating a proxy_strategy + */ + enum aws_http_proxy_authentication_type auth_type; + + /** + * @Deprecated - Optional user name to use for basic authentication + * Replaced by instantiating a proxy_strategy via aws_http_proxy_strategy_new_basic_auth() + */ + struct aws_byte_cursor auth_username; + + /** + * @Deprecated - Optional password to use for basic authentication + * Replaced by instantiating a proxy_strategy via aws_http_proxy_strategy_new_basic_auth() + */ + struct aws_byte_cursor auth_password; +}; + +/** + * Synchronous (for now) callback function to fetch a token used in modifying CONNECT requests + */ +typedef struct aws_string *(aws_http_proxy_negotiation_get_token_sync_fn)(void *user_data, int *out_error_code); + +/** + * Synchronous (for now) callback function to fetch a token used in modifying CONNECT request. Includes a (byte string) + * context intended to be used as part of a challenge-response flow. + */ +typedef struct aws_string *(aws_http_proxy_negotiation_get_challenge_token_sync_fn)( + void *user_data, + const struct aws_byte_cursor *challenge_context, + int *out_error_code); + +/** + * Proxy negotiation logic must call this function to indicate an unsuccessful outcome + */ +typedef void(aws_http_proxy_negotiation_terminate_fn)( + struct aws_http_message *message, + int error_code, + void *internal_proxy_user_data); + +/** + * Proxy negotiation logic must call this function to forward the potentially-mutated request back to the proxy + * connection logic. + */ +typedef void(aws_http_proxy_negotiation_http_request_forward_fn)( + struct aws_http_message *message, + void *internal_proxy_user_data); + +/** + * User-supplied transform callback which implements the proxy request flow and ultimately, across all execution + * pathways, invokes either the terminate function or the forward function appropriately. + * + * For tunneling proxy connections, this request flow transform only applies to the CONNECT stage of proxy + * connection establishment. + * + * For forwarding proxy connections, this request flow transform applies to every single http request that goes + * out on the connection. + * + * Forwarding proxy connections cannot yet support a truly async request transform without major surgery on http + * stream creation, so for now, we split into an async version (for tunneling proxies) and a separate + * synchronous version for forwarding proxies. Also forwarding proxies are a kind of legacy dead-end in some + * sense. + * + */ +typedef void(aws_http_proxy_negotiation_http_request_transform_async_fn)( + struct aws_http_proxy_negotiator *proxy_negotiator, + struct aws_http_message *message, + aws_http_proxy_negotiation_terminate_fn *negotiation_termination_callback, + aws_http_proxy_negotiation_http_request_forward_fn *negotiation_http_request_forward_callback, + void *internal_proxy_user_data); + +typedef int(aws_http_proxy_negotiation_http_request_transform_fn)( + struct aws_http_proxy_negotiator *proxy_negotiator, + struct aws_http_message *message); + +/** + * Tunneling proxy connections only. A callback that lets the negotiator examine the headers in the + * response to the most recent CONNECT request as they arrive. + */ +typedef int(aws_http_proxy_negotiation_connect_on_incoming_headers_fn)( + struct aws_http_proxy_negotiator *proxy_negotiator, + enum aws_http_header_block header_block, + const struct aws_http_header *header_array, + size_t num_headers); + +/** + * Tunneling proxy connections only. A callback that lets the negotiator examine the status code of the + * response to the most recent CONNECT request. + */ +typedef int(aws_http_proxy_negotiator_connect_status_fn)( + struct aws_http_proxy_negotiator *proxy_negotiator, + enum aws_http_status_code status_code); + +/** + * Tunneling proxy connections only. A callback that lets the negotiator examine the body of the response + * to the most recent CONNECT request. + */ +typedef int(aws_http_proxy_negotiator_connect_on_incoming_body_fn)( + struct aws_http_proxy_negotiator *proxy_negotiator, + const struct aws_byte_cursor *data); + +/* + * Control value that lets the http proxy implementation know if and how to retry a CONNECT request based on + * the proxy negotiator's state. + */ +enum aws_http_proxy_negotiation_retry_directive { + /* + * Stop trying to connect through the proxy and give up. + */ + AWS_HPNRD_STOP, + + /* + * Establish a new connection to the proxy before making the next CONNECT request + */ + AWS_HPNRD_NEW_CONNECTION, + + /* + * Reuse the existing connection to make the next CONNECT request + */ + AWS_HPNRD_CURRENT_CONNECTION, +}; + +typedef enum aws_http_proxy_negotiation_retry_directive(aws_http_proxy_negotiator_get_retry_directive_fn)( + struct aws_http_proxy_negotiator *proxy_negotiator); + +/** + * Vtable for forwarding-based proxy negotiators + */ +struct aws_http_proxy_negotiator_forwarding_vtable { + aws_http_proxy_negotiation_http_request_transform_fn *forward_request_transform; +}; + +/** + * Vtable for tunneling-based proxy negotiators + */ +struct aws_http_proxy_negotiator_tunnelling_vtable { + aws_http_proxy_negotiation_http_request_transform_async_fn *connect_request_transform; + + aws_http_proxy_negotiation_connect_on_incoming_headers_fn *on_incoming_headers_callback; + aws_http_proxy_negotiator_connect_status_fn *on_status_callback; + aws_http_proxy_negotiator_connect_on_incoming_body_fn *on_incoming_body_callback; + + aws_http_proxy_negotiator_get_retry_directive_fn *get_retry_directive; +}; + +/* + * Base definition of a proxy negotiator. + * + * A negotiator works differently based on what kind of proxy connection is being asked for: + * + * (1) Tunneling - In a tunneling proxy connection, the connect_request_transform is invoked on every CONNECT request. + * The connect_request_transform implementation *MUST*, in turn, eventually call one of the terminate or forward + * functions it gets supplied with. + * + * Every CONNECT request, if a response is obtained, will properly invoke the response handling callbacks supplied + * in the tunneling vtable. + * + * (2) Forwarding - In a forwarding proxy connection, the forward_request_transform is invoked on every request sent out + * on the connection. + */ +struct aws_http_proxy_negotiator { + struct aws_ref_count ref_count; + + void *impl; + + union { + struct aws_http_proxy_negotiator_forwarding_vtable *forwarding_vtable; + struct aws_http_proxy_negotiator_tunnelling_vtable *tunnelling_vtable; + } strategy_vtable; +}; + +/*********************************************************************************************/ + +typedef struct aws_http_proxy_negotiator *(aws_http_proxy_strategy_create_negotiator_fn)( + struct aws_http_proxy_strategy *proxy_strategy, + struct aws_allocator *allocator); + +struct aws_http_proxy_strategy_vtable { + aws_http_proxy_strategy_create_negotiator_fn *create_negotiator; +}; + +struct aws_http_proxy_strategy { + struct aws_ref_count ref_count; + struct aws_http_proxy_strategy_vtable *vtable; + void *impl; + enum aws_http_proxy_connection_type proxy_connection_type; +}; + +/* + * Options necessary to create a basic authentication proxy strategy + */ +struct aws_http_proxy_strategy_basic_auth_options { + + /* type of proxy connection being established, must be forwarding or tunnel */ + enum aws_http_proxy_connection_type proxy_connection_type; + + /* user name to use in basic authentication */ + struct aws_byte_cursor user_name; + + /* password to use in basic authentication */ + struct aws_byte_cursor password; +}; + +/* + * Options necessary to create a (synchronous) kerberos authentication proxy strategy + */ +struct aws_http_proxy_strategy_tunneling_kerberos_options { + + aws_http_proxy_negotiation_get_token_sync_fn *get_token; + + void *get_token_user_data; +}; + +/* + * Options necessary to create a (synchronous) ntlm authentication proxy strategy + */ +struct aws_http_proxy_strategy_tunneling_ntlm_options { + + aws_http_proxy_negotiation_get_token_sync_fn *get_token; + + aws_http_proxy_negotiation_get_challenge_token_sync_fn *get_challenge_token; + + void *get_challenge_token_user_data; +}; + +/* + * Options necessary to create an adaptive sequential strategy that tries one or more of kerberos and ntlm (in that + * order, if both are active). If an options struct is NULL, then that strategy will not be used. + */ +struct aws_http_proxy_strategy_tunneling_adaptive_options { + /* + * If non-null, will insert a kerberos proxy strategy into the adaptive sequence + */ + struct aws_http_proxy_strategy_tunneling_kerberos_options *kerberos_options; + + /* + * If non-null will insert an ntlm proxy strategy into the adaptive sequence + */ + struct aws_http_proxy_strategy_tunneling_ntlm_options *ntlm_options; +}; + +/* + * Options necessary to create a sequential proxy strategy. + */ +struct aws_http_proxy_strategy_tunneling_sequence_options { + struct aws_http_proxy_strategy **strategies; + + uint32_t strategy_count; +}; + +AWS_EXTERN_C_BEGIN + +/** + * Take a reference to an http proxy negotiator + * @param proxy_negotiator negotiator to take a reference to + * @return the strategy + */ +AWS_HTTP_API +struct aws_http_proxy_negotiator *aws_http_proxy_negotiator_acquire(struct aws_http_proxy_negotiator *proxy_negotiator); + +/** + * Release a reference to an http proxy negotiator + * @param proxy_negotiator negotiator to release a reference to + */ +AWS_HTTP_API +void aws_http_proxy_negotiator_release(struct aws_http_proxy_negotiator *proxy_negotiator); + +/** + * Creates a new proxy negotiator from a proxy strategy + * @param allocator memory allocator to use + * @param strategy strategy to creation a new negotiator for + * @return a new proxy negotiator if successful, otherwise NULL + */ +AWS_HTTP_API +struct aws_http_proxy_negotiator *aws_http_proxy_strategy_create_negotiator( + struct aws_http_proxy_strategy *strategy, + struct aws_allocator *allocator); + +/** + * Take a reference to an http proxy strategy + * @param proxy_strategy strategy to take a reference to + * @return the strategy + */ +AWS_HTTP_API +struct aws_http_proxy_strategy *aws_http_proxy_strategy_acquire(struct aws_http_proxy_strategy *proxy_strategy); + +/** + * Release a reference to an http proxy strategy + * @param proxy_strategy strategy to release a reference to + */ +AWS_HTTP_API +void aws_http_proxy_strategy_release(struct aws_http_proxy_strategy *proxy_strategy); + +/** + * A constructor for a proxy strategy that performs basic authentication by adding the appropriate + * header and header value to requests or CONNECT requests. + * + * @param allocator memory allocator to use + * @param config basic authentication configuration info + * @return a new proxy strategy if successfully constructed, otherwise NULL + */ +AWS_HTTP_API +struct aws_http_proxy_strategy *aws_http_proxy_strategy_new_basic_auth( + struct aws_allocator *allocator, + struct aws_http_proxy_strategy_basic_auth_options *config); + +/** + * Constructor for an adaptive tunneling proxy strategy. This strategy attempts a vanilla CONNECT and if that + * fails it may make followup CONNECT attempts using kerberos or ntlm tokens, based on configuration and proxy + * response properties. + * + * @param allocator memory allocator to use + * @param config configuration options for the strategy + * @return a new proxy strategy if successfully constructed, otherwise NULL + */ +AWS_HTTP_API +struct aws_http_proxy_strategy *aws_http_proxy_strategy_new_tunneling_adaptive( + struct aws_allocator *allocator, + struct aws_http_proxy_strategy_tunneling_adaptive_options *config); + +/* + * aws_http_proxy_config is the persistent, memory-managed version of aws_http_proxy_options + * + * This is a set of APIs for creating, destroying and converting between them + */ + +/** + * Create a persistent proxy configuration from http connection options + * @param allocator memory allocator to use + * @param options http connection options to source proxy configuration from + * @return + */ +AWS_HTTP_API +struct aws_http_proxy_config *aws_http_proxy_config_new_from_connection_options( + struct aws_allocator *allocator, + const struct aws_http_client_connection_options *options); + +/** + * Create a persistent proxy configuration from http connection manager options + * @param allocator memory allocator to use + * @param options http connection manager options to source proxy configuration from + * @return + */ +AWS_HTTP_API +struct aws_http_proxy_config *aws_http_proxy_config_new_from_manager_options( + struct aws_allocator *allocator, + const struct aws_http_connection_manager_options *options); + +/** + * Create a persistent proxy configuration from non-persistent proxy options. The resulting + * proxy configuration assumes a tunneling connection type. + * + * @param allocator memory allocator to use + * @param options http proxy options to source proxy configuration from + * @return + */ +AWS_HTTP_API +struct aws_http_proxy_config *aws_http_proxy_config_new_tunneling_from_proxy_options( + struct aws_allocator *allocator, + const struct aws_http_proxy_options *options); + +/** + * Clones an existing proxy configuration. A refactor could remove this (do a "move" between the old and new user + * data in the one spot it's used) but that should wait until we have better test cases for the logic where this + * gets invoked (ntlm/kerberos chains). + * + * @param allocator memory allocator to use + * @param proxy_config http proxy configuration to clone + * @return + */ +AWS_HTTP_API +struct aws_http_proxy_config *aws_http_proxy_config_new_clone( + struct aws_allocator *allocator, + const struct aws_http_proxy_config *proxy_config); + +/** + * Destroys an http proxy configuration + * @param config http proxy configuration to destroy + */ +AWS_HTTP_API +void aws_http_proxy_config_destroy(struct aws_http_proxy_config *config); + +/** + * Initializes non-persistent http proxy options from a persistent http proxy configuration + * @param options http proxy options to initialize + * @param config the http proxy config to use as an initialization source + */ +AWS_HTTP_API +void aws_http_proxy_options_init_from_config( + struct aws_http_proxy_options *options, + const struct aws_http_proxy_config *config); + +AWS_EXTERN_C_END + +#endif /* AWS_PROXY_H */ diff --git a/source/connection_manager.c b/source/connection_manager.c index 1d276de83..8a38f7cf5 100644 --- a/source/connection_manager.c +++ b/source/connection_manager.c @@ -808,7 +808,7 @@ struct aws_http_connection_manager *aws_http_connection_manager_new( } if (options->proxy_options) { - manager->proxy_config = aws_http_proxy_config_new(allocator, options->proxy_options); + manager->proxy_config = aws_http_proxy_config_new_from_manager_options(allocator, options); if (manager->proxy_config == NULL) { goto on_error; } diff --git a/source/http.c b/source/http.c index f31fa06e6..86100866d 100644 --- a/source/http.c +++ b/source/http.c @@ -86,8 +86,8 @@ static struct aws_error_info s_errors[] = { AWS_ERROR_HTTP_SERVER_CLOSED, "The http server is closed, no more connections will be accepted"), AWS_DEFINE_ERROR_INFO_HTTP( - AWS_ERROR_HTTP_PROXY_TLS_CONNECT_FAILED, - "Proxy tls connection establishment failed because the CONNECT call failed"), + AWS_ERROR_HTTP_PROXY_CONNECT_FAILED, + "Proxy-based connection establishment failed because the CONNECT call failed"), AWS_DEFINE_ERROR_INFO_HTTP( AWS_ERROR_HTTP_CONNECTION_MANAGER_SHUTTING_DOWN, "Connection acquisition failed because connection manager is shutting down"), @@ -115,6 +115,15 @@ static struct aws_error_info s_errors[] = { AWS_DEFINE_ERROR_INFO_HTTP( AWS_ERROR_HTTP_STREAM_HAS_COMPLETED, "HTTP-stream has completed, action cannot be performed."), + AWS_DEFINE_ERROR_INFO_HTTP( + AWS_ERROR_HTTP_PROXY_STRATEGY_NTLM_CHALLENGE_TOKEN_MISSING, + "NTLM Proxy strategy was initiated without a challenge token"), + AWS_DEFINE_ERROR_INFO_HTTP( + AWS_ERROR_HTTP_PROXY_STRATEGY_TOKEN_RETRIEVAL_FAILURE, + "Failure in user code while retrieving proxy auth token"), + AWS_DEFINE_ERROR_INFO_HTTP( + AWS_ERROR_HTTP_PROXY_CONNECT_FAILED_RETRYABLE, + "Proxy connection attempt failed but the negotiation could be continued on a new connection"), }; /* clang-format on */ @@ -133,6 +142,10 @@ static struct aws_log_subject_info s_log_subject_infos[] = { DEFINE_LOG_SUBJECT_INFO(AWS_LS_HTTP_CONNECTION_MANAGER, "connection-manager", "HTTP connection manager"), DEFINE_LOG_SUBJECT_INFO(AWS_LS_HTTP_WEBSOCKET, "websocket", "Websocket"), DEFINE_LOG_SUBJECT_INFO(AWS_LS_HTTP_WEBSOCKET_SETUP, "websocket-setup", "Websocket setup"), + DEFINE_LOG_SUBJECT_INFO( + AWS_LS_HTTP_PROXY_NEGOTIATION, + "proxy-negotiation", + "Negotiating an http connection with a proxy server"), }; static struct aws_log_subject_info_list s_log_subject_list = { diff --git a/source/proxy_connection.c b/source/proxy_connection.c index 49c160154..cf44efc34 100644 --- a/source/proxy_connection.c +++ b/source/proxy_connection.c @@ -7,7 +7,9 @@ #include #include +#include #include +#include #include #include #include @@ -20,8 +22,6 @@ #endif AWS_STATIC_STRING_FROM_LITERAL(s_host_header_name, "Host"); -AWS_STATIC_STRING_FROM_LITERAL(s_proxy_authorization_header_name, "Proxy-Authorization"); -AWS_STATIC_STRING_FROM_LITERAL(s_proxy_authorization_header_basic_prefix, "Basic "); AWS_STATIC_STRING_FROM_LITERAL(s_proxy_connection_header_name, "Proxy-Connection"); AWS_STATIC_STRING_FROM_LITERAL(s_proxy_connection_header_value, "Keep-Alive"); AWS_STATIC_STRING_FROM_LITERAL(s_options_method, "OPTIONS"); @@ -53,6 +53,10 @@ void aws_http_proxy_user_data_destroy(struct aws_http_proxy_user_data *user_data aws_mem_release(user_data->allocator, user_data->tls_options); } + aws_http_proxy_negotiator_release(user_data->proxy_negotiator); + + aws_client_bootstrap_release(user_data->bootstrap); + aws_mem_release(user_data->allocator, user_data); } @@ -70,6 +74,13 @@ struct aws_http_proxy_user_data *aws_http_proxy_user_data_new( user_data->allocator = allocator; user_data->state = AWS_PBS_SOCKET_CONNECT; user_data->error_code = AWS_ERROR_SUCCESS; + user_data->connect_status_code = AWS_HTTP_STATUS_CODE_UNKNOWN; + user_data->bootstrap = aws_client_bootstrap_acquire(options->bootstrap); + if (options->socket_options != NULL) { + user_data->socket_options = *options->socket_options; + } + user_data->manual_window_management = options->manual_window_management; + user_data->initial_window_size = options->initial_window_size; user_data->original_host = aws_string_new_from_cursor(allocator, &options->host_name); if (user_data->original_host == NULL) { @@ -78,11 +89,17 @@ struct aws_http_proxy_user_data *aws_http_proxy_user_data_new( user_data->original_port = options->port; - user_data->proxy_config = aws_http_proxy_config_new(allocator, options->proxy_options); + user_data->proxy_config = aws_http_proxy_config_new_from_connection_options(allocator, options); if (user_data->proxy_config == NULL) { goto on_error; } + user_data->proxy_negotiator = + aws_http_proxy_strategy_create_negotiator(user_data->proxy_config->proxy_strategy, allocator); + if (user_data->proxy_negotiator == NULL) { + goto on_error; + } + if (options->tls_options) { /* clone tls options, but redirect user data to what we're creating */ user_data->tls_options = aws_mem_calloc(allocator, 1, sizeof(struct aws_tls_connection_options)); @@ -113,90 +130,78 @@ struct aws_http_proxy_user_data *aws_http_proxy_user_data_new( return NULL; } -/* - * Adds a proxy authentication header based on the basic authentication mode, rfc7617 - */ -static int s_add_basic_proxy_authentication_header( - struct aws_http_message *request, - struct aws_http_proxy_user_data *proxy_user_data) { - - struct aws_byte_buf base64_input_value; - AWS_ZERO_STRUCT(base64_input_value); - - struct aws_byte_buf header_value; - AWS_ZERO_STRUCT(header_value); - - int result = AWS_OP_ERR; +struct aws_http_proxy_user_data *aws_http_proxy_user_data_new_reset_clone( + struct aws_allocator *allocator, + struct aws_http_proxy_user_data *old_user_data) { - if (aws_byte_buf_init( - &base64_input_value, - proxy_user_data->allocator, - proxy_user_data->proxy_config->auth_username.len + proxy_user_data->proxy_config->auth_password.len + 1)) { - goto done; - } + AWS_FATAL_ASSERT(old_user_data != NULL); - /* First build a buffer with "username:password" in it */ - struct aws_byte_cursor username_cursor = aws_byte_cursor_from_buf(&proxy_user_data->proxy_config->auth_username); - if (aws_byte_buf_append(&base64_input_value, &username_cursor)) { - goto done; + struct aws_http_proxy_user_data *user_data = aws_mem_calloc(allocator, 1, sizeof(struct aws_http_proxy_user_data)); + if (user_data == NULL) { + return NULL; } - struct aws_byte_cursor colon_cursor = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL(":"); - if (aws_byte_buf_append(&base64_input_value, &colon_cursor)) { - goto done; - } + user_data->allocator = allocator; + user_data->state = AWS_PBS_SOCKET_CONNECT; + user_data->error_code = AWS_ERROR_SUCCESS; + user_data->connect_status_code = AWS_HTTP_STATUS_CODE_UNKNOWN; + user_data->bootstrap = aws_client_bootstrap_acquire(old_user_data->bootstrap); + user_data->socket_options = old_user_data->socket_options; + user_data->manual_window_management = old_user_data->manual_window_management; + user_data->initial_window_size = old_user_data->initial_window_size; - struct aws_byte_cursor password_cursor = aws_byte_cursor_from_buf(&proxy_user_data->proxy_config->auth_password); - if (aws_byte_buf_append(&base64_input_value, &password_cursor)) { - goto done; + user_data->original_host = aws_string_new_from_string(allocator, old_user_data->original_host); + if (user_data->original_host == NULL) { + goto on_error; } - struct aws_byte_cursor base64_source_cursor = - aws_byte_cursor_from_array(base64_input_value.buffer, base64_input_value.len); + user_data->original_port = old_user_data->original_port; - /* Figure out how much room we need in our final header value buffer */ - size_t required_size = 0; - if (aws_base64_compute_encoded_len(base64_source_cursor.len, &required_size)) { - goto done; + user_data->proxy_config = aws_http_proxy_config_new_clone(allocator, old_user_data->proxy_config); + if (user_data->proxy_config == NULL) { + goto on_error; } - required_size += s_proxy_authorization_header_basic_prefix->len + 1; - if (aws_byte_buf_init(&header_value, proxy_user_data->allocator, required_size)) { - goto done; + user_data->proxy_negotiator = aws_http_proxy_negotiator_acquire(old_user_data->proxy_negotiator); + if (user_data->proxy_negotiator == NULL) { + goto on_error; } - /* Build the final header value by appending the authorization type and the base64 encoding string together */ - struct aws_byte_cursor basic_prefix = aws_byte_cursor_from_string(s_proxy_authorization_header_basic_prefix); - if (aws_byte_buf_append_dynamic(&header_value, &basic_prefix)) { - goto done; - } + if (old_user_data->tls_options) { + /* clone tls options, but redirect user data to what we're creating */ + user_data->tls_options = aws_mem_calloc(allocator, 1, sizeof(struct aws_tls_connection_options)); + if (user_data->tls_options == NULL || + aws_tls_connection_options_copy(user_data->tls_options, old_user_data->tls_options)) { + goto on_error; + } - if (aws_base64_encode(&base64_source_cursor, &header_value)) { - goto done; + user_data->tls_options->user_data = user_data; } - struct aws_http_header header = {.name = aws_byte_cursor_from_string(s_proxy_authorization_header_name), - .value = aws_byte_cursor_from_array(header_value.buffer, header_value.len)}; + user_data->original_on_setup = old_user_data->original_on_setup; + user_data->original_on_shutdown = old_user_data->original_on_shutdown; + user_data->original_user_data = old_user_data->original_user_data; - if (aws_http_message_add_header(request, header)) { - goto done; - } + return user_data; - result = AWS_OP_SUCCESS; +on_error: -done: + AWS_LOGF_ERROR( + AWS_LS_HTTP_CONNECTION, + "(STATIC) Proxy connection failed to create user data with error %d(%s)", + aws_last_error(), + aws_error_str(aws_last_error())); - aws_byte_buf_clean_up(&header_value); - aws_byte_buf_clean_up(&base64_input_value); + aws_http_proxy_user_data_destroy(user_data); - return result; + return NULL; } /* * Connection callback used ONLY by http proxy connections. After this, * the connection is live and the user is notified */ -static void s_aws_http_on_client_connection_http_proxy_setup_fn( +static void s_aws_http_on_client_connection_http_forwarding_proxy_setup_fn( struct aws_http_connection *connection, int error_code, void *user_data) { @@ -235,14 +240,16 @@ static void s_aws_http_on_client_connection_http_proxy_shutdown_fn( ec = AWS_ERROR_UNKNOWN; } - AWS_LOGF_ERROR( + AWS_LOGF_WARN( AWS_LS_HTTP_CONNECTION, "(%p) Error %d while connecting to \"%s\" via proxy.", (void *)connection, ec, (char *)proxy_ud->original_host->bytes); - proxy_ud->original_on_setup(NULL, ec, proxy_ud->original_user_data); + if (proxy_ud->original_on_setup != NULL) { + proxy_ud->original_on_setup(NULL, ec, proxy_ud->original_user_data); + } } aws_http_proxy_user_data_destroy(user_data); @@ -271,8 +278,11 @@ static void s_aws_http_proxy_user_data_shutdown(struct aws_http_proxy_user_data user_data->connect_request = NULL; } - aws_http_connection_release(user_data->connection); + struct aws_http_connection *http_connection = user_data->connection; user_data->connection = NULL; + + aws_channel_shutdown(http_connection->channel_slot->channel, user_data->error_code); + aws_http_connection_release(http_connection); } /* @@ -318,23 +328,22 @@ static struct aws_http_message *s_build_proxy_connect_request(struct aws_http_pr goto on_error; } - struct aws_http_header host_header = {.name = aws_byte_cursor_from_string(s_host_header_name), - .value = aws_byte_cursor_from_string(user_data->original_host)}; + struct aws_http_header host_header = { + .name = aws_byte_cursor_from_string(s_host_header_name), + .value = aws_byte_cursor_from_array(path_buffer.buffer, path_buffer.len), + }; if (aws_http_message_add_header(request, host_header)) { goto on_error; } - struct aws_http_header keep_alive_header = {.name = aws_byte_cursor_from_string(s_proxy_connection_header_name), - .value = aws_byte_cursor_from_string(s_proxy_connection_header_value)}; + struct aws_http_header keep_alive_header = { + .name = aws_byte_cursor_from_string(s_proxy_connection_header_name), + .value = aws_byte_cursor_from_string(s_proxy_connection_header_value), + }; if (aws_http_message_add_header(request, keep_alive_header)) { goto on_error; } - if (user_data->proxy_config->auth_type == AWS_HPAT_BASIC && - s_add_basic_proxy_authentication_header(request, user_data)) { - goto on_error; - } - aws_byte_buf_clean_up(&path_buffer); return request; @@ -354,10 +363,46 @@ static struct aws_http_message *s_build_proxy_connect_request(struct aws_http_pr return NULL; } +static int s_aws_http_on_incoming_body_tunnel_proxy( + struct aws_http_stream *stream, + const struct aws_byte_cursor *data, + void *user_data) { + (void)stream; + + struct aws_http_proxy_user_data *context = user_data; + aws_http_proxy_negotiator_connect_on_incoming_body_fn *on_incoming_body = + context->proxy_negotiator->strategy_vtable.tunnelling_vtable->on_incoming_body_callback; + if (on_incoming_body != NULL) { + (*on_incoming_body)(context->proxy_negotiator, data); + } + + aws_http_stream_update_window(stream, data->len); + + return AWS_OP_SUCCESS; +} + +static int s_aws_http_on_response_headers_tunnel_proxy( + struct aws_http_stream *stream, + enum aws_http_header_block header_block, + const struct aws_http_header *header_array, + size_t num_headers, + void *user_data) { + (void)stream; + + struct aws_http_proxy_user_data *context = user_data; + aws_http_proxy_negotiation_connect_on_incoming_headers_fn *on_incoming_headers = + context->proxy_negotiator->strategy_vtable.tunnelling_vtable->on_incoming_headers_callback; + if (on_incoming_headers != NULL) { + (*on_incoming_headers)(context->proxy_negotiator, header_block, header_array, num_headers); + } + + return AWS_OP_SUCCESS; +} + /* * Headers done callback for the CONNECT request made during tls proxy connections */ -static int s_aws_http_on_incoming_header_block_done_tls_proxy( +static int s_aws_http_on_incoming_header_block_done_tunnel_proxy( struct aws_http_stream *stream, enum aws_http_header_block header_block, void *user_data) { @@ -365,14 +410,22 @@ static int s_aws_http_on_incoming_header_block_done_tls_proxy( struct aws_http_proxy_user_data *context = user_data; if (header_block == AWS_HTTP_HEADER_BLOCK_MAIN) { - int status = 0; - if (aws_http_stream_get_incoming_response_status(stream, &status) || status != 200) { + int status_code = AWS_HTTP_STATUS_CODE_UNKNOWN; + aws_http_stream_get_incoming_response_status(stream, &status_code); + context->connect_status_code = (enum aws_http_status_code)status_code; + if (context->connect_status_code != AWS_HTTP_STATUS_CODE_200_OK) { AWS_LOGF_ERROR( AWS_LS_HTTP_CONNECTION, "(%p) Proxy CONNECT request failed with status code %d", (void *)context->connection, - status); - context->error_code = AWS_ERROR_HTTP_PROXY_TLS_CONNECT_FAILED; + context->connect_status_code); + context->error_code = AWS_ERROR_HTTP_PROXY_CONNECT_FAILED; + } + + aws_http_proxy_negotiator_connect_status_fn *on_status = + context->proxy_negotiator->strategy_vtable.tunnelling_vtable->on_status_callback; + if (on_status != NULL) { + (*on_status)(context->proxy_negotiator, context->connect_status_code); } } @@ -408,10 +461,16 @@ static void s_on_origin_server_tls_negotation_result( context->original_on_setup(context->connection, AWS_ERROR_SUCCESS, context->original_user_data); } +static int s_create_tunneling_connection(struct aws_http_proxy_user_data *user_data); +static int s_make_proxy_connect_request(struct aws_http_proxy_user_data *user_data); + /* * Stream done callback for the CONNECT request made during tls proxy connections */ -static void s_aws_http_on_stream_complete_tls_proxy(struct aws_http_stream *stream, int error_code, void *user_data) { +static void s_aws_http_on_stream_complete_tunnel_proxy( + struct aws_http_stream *stream, + int error_code, + void *user_data) { struct aws_http_proxy_user_data *context = user_data; AWS_FATAL_ASSERT(stream == context->connect_stream); @@ -420,6 +479,32 @@ static void s_aws_http_on_stream_complete_tls_proxy(struct aws_http_stream *stre } if (context->error_code != AWS_ERROR_SUCCESS) { + context->error_code = AWS_ERROR_HTTP_PROXY_CONNECT_FAILED; + if (context->connect_status_code == AWS_HTTP_STATUS_CODE_407_PROXY_AUTHENTICATION_REQUIRED) { + enum aws_http_proxy_negotiation_retry_directive retry_directive = + aws_http_proxy_negotiator_get_retry_directive(context->proxy_negotiator); + + if (retry_directive == AWS_HPNRD_NEW_CONNECTION) { + struct aws_http_proxy_user_data *new_context = + aws_http_proxy_user_data_new_reset_clone(context->allocator, context); + if (new_context != NULL && s_create_tunneling_connection(new_context) == AWS_OP_SUCCESS) { + /* + * We successfully kicked off a new connection. By NULLing the callbacks on the old one, we can + * shut it down quietly without the user being notified. The new connection will notify the user + * based on its success or failure. + */ + context->original_on_shutdown = NULL; + context->original_on_setup = NULL; + context->error_code = AWS_ERROR_HTTP_PROXY_CONNECT_FAILED_RETRYABLE; + } + } else if (retry_directive == AWS_HPNRD_CURRENT_CONNECTION) { + context->error_code = AWS_ERROR_SUCCESS; + if (s_make_proxy_connect_request(context) == AWS_OP_SUCCESS) { + return; + } + } + } + s_aws_http_proxy_user_data_shutdown(context); return; } @@ -440,87 +525,122 @@ static void s_aws_http_on_stream_complete_tls_proxy(struct aws_http_stream *stre AWS_LOGF_INFO(AWS_LS_HTTP_CONNECTION, "(%p) Beginning TLS negotiation", (void *)context->connection); - /* - * Perform TLS negotiation to the origin server through proxy - */ - context->tls_options->on_negotiation_result = s_on_origin_server_tls_negotation_result; - - context->state = AWS_PBS_TLS_NEGOTIATION; - struct aws_channel *channel = aws_http_connection_get_channel(context->connection); + if (context->tls_options != NULL) { + /* + * Perform TLS negotiation to the origin server through proxy + */ + context->tls_options->on_negotiation_result = s_on_origin_server_tls_negotation_result; + + context->state = AWS_PBS_TLS_NEGOTIATION; + struct aws_channel *channel = aws_http_connection_get_channel(context->connection); + + struct aws_channel_slot *left_of_tls_slot = aws_channel_get_first_slot(channel); + if (context->proxy_config->tls_options != NULL) { + /* + * If making secure (double TLS) proxy connection, we need to go after the second slot: + * + * Socket -> TLS(proxy) -> TLS(origin server) -> Http + */ + left_of_tls_slot = left_of_tls_slot->adj_right; + } - struct aws_channel_slot *left_of_tls_slot = aws_channel_get_first_slot(channel); - if (context->proxy_config->tls_options != NULL) { + if (s_vtable->setup_client_tls(left_of_tls_slot, context->tls_options)) { + AWS_LOGF_ERROR( + AWS_LS_HTTP_CONNECTION, + "(%p) Proxy connection failed to start TLS negotiation with error %d(%s)", + (void *)context->connection, + aws_last_error(), + aws_error_str(aws_last_error())); + s_aws_http_proxy_user_data_shutdown(context); + return; + } + } else { /* - * If making secure (double TLS) proxy connection, we need to go after the second slot: - * - * Socket -> TLS(proxy) -> TLS(origin server) -> Http + * The tunnel has been established. */ - left_of_tls_slot = left_of_tls_slot->adj_right; + context->state = AWS_PBS_SUCCESS; + context->original_on_setup(context->connection, AWS_ERROR_SUCCESS, context->original_user_data); } +} - if (s_vtable->setup_client_tls(left_of_tls_slot, context->tls_options)) { - AWS_LOGF_ERROR( - AWS_LS_HTTP_CONNECTION, - "(%p) Proxy connection failed to start TLS negotiation with error %d(%s)", - (void *)context->connection, - aws_last_error(), - aws_error_str(aws_last_error())); - s_aws_http_proxy_user_data_shutdown(context); - return; - } +static void s_terminate_tunneling_connect( + struct aws_http_message *message, + int error_code, + void *internal_proxy_user_data) { + (void)message; + + struct aws_http_proxy_user_data *proxy_ud = internal_proxy_user_data; + + AWS_LOGF_ERROR( + AWS_LS_HTTP_CONNECTION, + "(%p) Tunneling proxy connection failed to create request stream for CONNECT request with error %d(%s)", + (void *)proxy_ud->connection, + error_code, + aws_error_str(error_code)); + + proxy_ud->error_code = error_code; + s_aws_http_proxy_user_data_shutdown(proxy_ud); } -/* - * Issues a CONNECT request on a newly-established proxy connection with the intent - * of upgrading with TLS on success - */ -static int s_make_proxy_connect_request( - struct aws_http_connection *connection, - struct aws_http_proxy_user_data *user_data) { - struct aws_http_message *request = s_build_proxy_connect_request(user_data); - if (request == NULL) { - return AWS_OP_ERR; - } +static void s_continue_tunneling_connect(struct aws_http_message *message, void *internal_proxy_user_data) { + struct aws_http_proxy_user_data *proxy_ud = internal_proxy_user_data; struct aws_http_make_request_options request_options = { .self_size = sizeof(request_options), - .request = request, - .user_data = user_data, - .on_response_header_block_done = s_aws_http_on_incoming_header_block_done_tls_proxy, - .on_complete = s_aws_http_on_stream_complete_tls_proxy, + .request = message, + .user_data = proxy_ud, + .on_response_headers = s_aws_http_on_response_headers_tunnel_proxy, + .on_response_header_block_done = s_aws_http_on_incoming_header_block_done_tunnel_proxy, + .on_response_body = s_aws_http_on_incoming_body_tunnel_proxy, + .on_complete = s_aws_http_on_stream_complete_tunnel_proxy, }; - struct aws_http_stream *stream = aws_http_connection_make_request(connection, &request_options); - if (stream == NULL) { - goto on_error; + if (proxy_ud->connect_stream != NULL) { + aws_http_stream_release(proxy_ud->connect_stream); } - user_data->connect_stream = stream; - user_data->connect_request = request; + proxy_ud->connect_stream = aws_http_connection_make_request(proxy_ud->connection, &request_options); + if (proxy_ud->connect_stream == NULL) { + goto on_error; + } - aws_http_stream_activate(stream); + aws_http_stream_activate(proxy_ud->connect_stream); - return AWS_OP_SUCCESS; + return; on_error: - AWS_LOGF_ERROR( - AWS_LS_HTTP_CONNECTION, - "(%p) Proxy connection failed to create request stream for CONNECT request with error %d(%s)", - (void *)connection, - aws_last_error(), - aws_error_str(aws_last_error())); + s_aws_http_proxy_user_data_shutdown(proxy_ud); +} - aws_http_message_destroy(request); +/* + * Issues a CONNECT request on an http connection + */ +static int s_make_proxy_connect_request(struct aws_http_proxy_user_data *user_data) { + if (user_data->connect_request != NULL) { + aws_http_message_destroy(user_data->connect_request); + user_data->connect_request = NULL; + } - return AWS_OP_ERR; + user_data->connect_request = s_build_proxy_connect_request(user_data); + if (user_data->connect_request == NULL) { + return AWS_OP_ERR; + } + + (*user_data->proxy_negotiator->strategy_vtable.tunnelling_vtable->connect_request_transform)( + user_data->proxy_negotiator, + user_data->connect_request, + s_terminate_tunneling_connect, + s_continue_tunneling_connect, + user_data); + + return AWS_OP_SUCCESS; } /* - * Connection setup callback for tls-based proxy connections. - * Could be unified with non-tls version by checking tls options and branching post-success + * Connection setup callback for tunneling proxy connections. */ -static void s_aws_http_on_client_connection_http_tls_proxy_setup_fn( +static void s_aws_http_on_client_connection_http_tunneling_proxy_setup_fn( struct aws_http_connection *connection, int error_code, void *user_data) { @@ -536,7 +656,7 @@ static void s_aws_http_on_client_connection_http_tls_proxy_setup_fn( proxy_ud->connection = connection; proxy_ud->state = AWS_PBS_HTTP_CONNECT; - if (s_make_proxy_connect_request(connection, proxy_ud)) { + if (s_make_proxy_connect_request(proxy_ud)) { goto on_error; } @@ -661,33 +781,22 @@ int aws_http_rewrite_uri_for_proxy_request( static int s_proxy_http_request_transform(struct aws_http_message *request, void *user_data) { struct aws_http_proxy_user_data *proxy_ud = user_data; - struct aws_byte_buf auth_header_value; - AWS_ZERO_STRUCT(auth_header_value); - - int result = AWS_OP_ERR; - - if (proxy_ud->proxy_config->auth_type == AWS_HPAT_BASIC && - s_add_basic_proxy_authentication_header(request, proxy_ud)) { - goto done; - } - if (aws_http_rewrite_uri_for_proxy_request(request, proxy_ud)) { - goto done; + return AWS_OP_ERR; } - result = AWS_OP_SUCCESS; - -done: - - aws_byte_buf_clean_up(&auth_header_value); + if ((*proxy_ud->proxy_negotiator->strategy_vtable.forwarding_vtable->forward_request_transform)( + proxy_ud->proxy_negotiator, request)) { + return AWS_OP_ERR; + } - return result; + return AWS_OP_SUCCESS; } /* * Top-level function to route a connection request through a proxy server, with no channel security */ -static int s_aws_http_client_connect_via_proxy_http(const struct aws_http_client_connection_options *options) { +static int s_aws_http_client_connect_via_forwarding_proxy(const struct aws_http_client_connection_options *options) { AWS_FATAL_ASSERT(options->tls_options == NULL); AWS_LOGF_INFO( @@ -711,7 +820,7 @@ static int s_aws_http_client_connect_via_proxy_http(const struct aws_http_client options_copy.host_name = options->proxy_options->host; options_copy.port = options->proxy_options->port; options_copy.user_data = proxy_user_data; - options_copy.on_setup = s_aws_http_on_client_connection_http_proxy_setup_fn; + options_copy.on_setup = s_aws_http_on_client_connection_http_forwarding_proxy_setup_fn; options_copy.on_shutdown = s_aws_http_on_client_connection_http_proxy_shutdown_fn; options_copy.tls_options = options->proxy_options->tls_options; @@ -729,17 +838,48 @@ static int s_aws_http_client_connect_via_proxy_http(const struct aws_http_client return result; } +static int s_create_tunneling_connection(struct aws_http_proxy_user_data *user_data) { + struct aws_http_client_connection_options connect_options; + AWS_ZERO_STRUCT(connect_options); + + connect_options.self_size = sizeof(struct aws_http_client_connection_options); + connect_options.allocator = user_data->allocator; + connect_options.bootstrap = user_data->bootstrap; + connect_options.host_name = aws_byte_cursor_from_buf(&user_data->proxy_config->host); + connect_options.port = user_data->proxy_config->port; + connect_options.socket_options = &user_data->socket_options; + connect_options.tls_options = user_data->proxy_config->tls_options; + connect_options.monitoring_options = NULL; /* ToDo */ + connect_options.manual_window_management = user_data->manual_window_management; + connect_options.initial_window_size = user_data->initial_window_size; + connect_options.user_data = user_data; + connect_options.on_setup = s_aws_http_on_client_connection_http_tunneling_proxy_setup_fn; + connect_options.on_shutdown = s_aws_http_on_client_connection_http_proxy_shutdown_fn; + connect_options.http1_options = NULL; /* ToDo */ + connect_options.http2_options = NULL; /* ToDo */ + + int result = aws_http_client_connect(&connect_options); + if (result == AWS_OP_ERR) { + AWS_LOGF_ERROR( + AWS_LS_HTTP_CONNECTION, + "(STATIC) Proxy tunnel connection failed client connect with error %d(%s)", + aws_last_error(), + aws_error_str(aws_last_error())); + aws_http_proxy_user_data_destroy(user_data); + } + + return result; +} + /* - * Top-level function to route a TLS connection through a proxy server + * Top-level function to route a connection through a proxy server via a CONNECT request */ -static int s_aws_http_client_connect_via_proxy_https(const struct aws_http_client_connection_options *options) { - - AWS_FATAL_ASSERT(options->tls_options != NULL); +static int s_aws_http_client_connect_via_tunneling_proxy(const struct aws_http_client_connection_options *options) { AWS_FATAL_ASSERT(options->proxy_options != NULL); AWS_LOGF_INFO( AWS_LS_HTTP_CONNECTION, - "(STATIC) Connecting to \"" PRInSTR "\" through TLS via proxy \"" PRInSTR "\"", + "(STATIC) Connecting to \"" PRInSTR "\" through a tunnel via proxy \"" PRInSTR "\"", AWS_BYTE_CURSOR_PRI(options->host_name), AWS_BYTE_CURSOR_PRI(options->proxy_options->host)); @@ -749,75 +889,173 @@ static int s_aws_http_client_connect_via_proxy_https(const struct aws_http_clien return AWS_OP_ERR; } - /* Fill in a new connection options pointing at the proxy */ - struct aws_http_client_connection_options options_copy = *options; - - options_copy.proxy_options = NULL; - options_copy.tls_options = NULL; - options_copy.host_name = options->proxy_options->host; - options_copy.port = options->proxy_options->port; - options_copy.user_data = user_data; - options_copy.on_setup = s_aws_http_on_client_connection_http_tls_proxy_setup_fn; - options_copy.on_shutdown = s_aws_http_on_client_connection_http_proxy_shutdown_fn; - options_copy.tls_options = options->proxy_options->tls_options; + return s_create_tunneling_connection(user_data); +} - int result = aws_http_client_connect(&options_copy); - if (result == AWS_OP_ERR) { - AWS_LOGF_ERROR( - AWS_LS_HTTP_CONNECTION, - "(STATIC) Proxy https connection failed client connect with error %d(%s)", - aws_last_error(), - aws_error_str(aws_last_error())); - aws_http_proxy_user_data_destroy(user_data); +static enum aws_http_proxy_connection_type s_determine_proxy_connection_type( + enum aws_http_proxy_connection_type proxy_connection_type, + const struct aws_tls_connection_options *tls_options) { + if (proxy_connection_type != AWS_HPCT_HTTP_LEGACY) { + return proxy_connection_type; } - return result; + if (tls_options != NULL) { + return AWS_HPCT_HTTP_TUNNEL; + } else { + return AWS_HPCT_HTTP_FORWARD; + } } /* * Dispatches a proxy-enabled connection request to the appropriate top-level connection function */ int aws_http_client_connect_via_proxy(const struct aws_http_client_connection_options *options) { - AWS_FATAL_ASSERT(options->proxy_options != NULL); + if (aws_http_options_validate_proxy_configuration(options)) { + return AWS_OP_ERR; + } - if (options->tls_options != NULL) { - return s_aws_http_client_connect_via_proxy_https(options); - } else { - return s_aws_http_client_connect_via_proxy_http(options); + enum aws_http_proxy_connection_type proxy_connection_type = + s_determine_proxy_connection_type(options->proxy_options->connection_type, options->tls_options); + + switch (proxy_connection_type) { + case AWS_HPCT_HTTP_FORWARD: + return s_aws_http_client_connect_via_forwarding_proxy(options); + + case AWS_HPCT_HTTP_TUNNEL: + return s_aws_http_client_connect_via_tunneling_proxy(options); + + default: + return aws_raise_error(AWS_ERROR_UNIMPLEMENTED); } } -struct aws_http_proxy_config *aws_http_proxy_config_new( +static struct aws_http_proxy_config *s_aws_http_proxy_config_new( struct aws_allocator *allocator, - const struct aws_http_proxy_options *options) { - AWS_FATAL_ASSERT(options != NULL); + const struct aws_http_proxy_options *proxy_options, + enum aws_http_proxy_connection_type override_proxy_connection_type) { + AWS_FATAL_ASSERT(proxy_options != NULL); + struct aws_http_proxy_config *config = aws_mem_calloc(allocator, 1, sizeof(struct aws_http_proxy_config)); if (config == NULL) { return NULL; } - if (aws_byte_buf_init_copy_from_cursor(&config->host, allocator, options->host)) { + config->connection_type = override_proxy_connection_type; + + if (aws_byte_buf_init_copy_from_cursor(&config->host, allocator, proxy_options->host)) { + goto on_error; } - if (aws_byte_buf_init_copy_from_cursor(&config->auth_username, allocator, options->auth_username)) { - goto on_error; + if (proxy_options->tls_options) { + config->tls_options = aws_mem_calloc(allocator, 1, sizeof(struct aws_tls_connection_options)); + if (aws_tls_connection_options_copy(config->tls_options, proxy_options->tls_options)) { + goto on_error; + } + } + + config->allocator = allocator; + config->port = proxy_options->port; + + if (proxy_options->proxy_strategy != NULL) { + config->proxy_strategy = aws_http_proxy_strategy_acquire(proxy_options->proxy_strategy); + } else if (proxy_options->auth_type == AWS_HPAT_BASIC) { + struct aws_http_proxy_strategy_basic_auth_options basic_config; + AWS_ZERO_STRUCT(basic_config); + + basic_config.proxy_connection_type = override_proxy_connection_type; + basic_config.user_name = proxy_options->auth_username; + basic_config.password = proxy_options->auth_password; + + config->proxy_strategy = aws_http_proxy_strategy_new_basic_auth(allocator, &basic_config); + } + + if (config->proxy_strategy == NULL) { + switch (override_proxy_connection_type) { + case AWS_HPCT_HTTP_FORWARD: + config->proxy_strategy = aws_http_proxy_strategy_new_forwarding_identity(allocator); + break; + + case AWS_HPCT_HTTP_TUNNEL: + config->proxy_strategy = aws_http_proxy_strategy_new_tunneling_one_time_identity(allocator); + break; + + default: + break; + } + + if (config->proxy_strategy == NULL) { + goto on_error; + } } - if (aws_byte_buf_init_copy_from_cursor(&config->auth_password, allocator, options->auth_password)) { + return config; + +on_error: + + aws_http_proxy_config_destroy(config); + + return NULL; +} + +struct aws_http_proxy_config *aws_http_proxy_config_new_from_connection_options( + struct aws_allocator *allocator, + const struct aws_http_client_connection_options *options) { + AWS_FATAL_ASSERT(options != NULL); + AWS_FATAL_ASSERT(options->proxy_options != NULL); + + return s_aws_http_proxy_config_new( + allocator, + options->proxy_options, + s_determine_proxy_connection_type(options->proxy_options->connection_type, options->tls_options)); +} + +struct aws_http_proxy_config *aws_http_proxy_config_new_from_manager_options( + struct aws_allocator *allocator, + const struct aws_http_connection_manager_options *options) { + AWS_FATAL_ASSERT(options != NULL); + AWS_FATAL_ASSERT(options->proxy_options != NULL); + + return s_aws_http_proxy_config_new( + allocator, + options->proxy_options, + s_determine_proxy_connection_type(options->proxy_options->connection_type, options->tls_connection_options)); +} + +struct aws_http_proxy_config *aws_http_proxy_config_new_tunneling_from_proxy_options( + struct aws_allocator *allocator, + const struct aws_http_proxy_options *proxy_options) { + + return s_aws_http_proxy_config_new(allocator, proxy_options, AWS_HPCT_HTTP_TUNNEL); +} + +struct aws_http_proxy_config *aws_http_proxy_config_new_clone( + struct aws_allocator *allocator, + const struct aws_http_proxy_config *proxy_config) { + + AWS_FATAL_ASSERT(proxy_config != NULL); + + struct aws_http_proxy_config *config = aws_mem_calloc(allocator, 1, sizeof(struct aws_http_proxy_config)); + if (config == NULL) { + return NULL; + } + + config->connection_type = proxy_config->connection_type; + + if (aws_byte_buf_init_copy_from_cursor(&config->host, allocator, aws_byte_cursor_from_buf(&proxy_config->host))) { goto on_error; } - if (options->tls_options) { + if (proxy_config->tls_options) { config->tls_options = aws_mem_calloc(allocator, 1, sizeof(struct aws_tls_connection_options)); - if (aws_tls_connection_options_copy(config->tls_options, options->tls_options)) { + if (aws_tls_connection_options_copy(config->tls_options, proxy_config->tls_options)) { goto on_error; } } config->allocator = allocator; - config->auth_type = options->auth_type; - config->port = options->port; + config->port = proxy_config->port; + config->proxy_strategy = aws_http_proxy_strategy_acquire(proxy_config->proxy_strategy); return config; @@ -834,14 +1072,14 @@ void aws_http_proxy_config_destroy(struct aws_http_proxy_config *config) { } aws_byte_buf_clean_up(&config->host); - aws_byte_buf_clean_up(&config->auth_username); - aws_byte_buf_clean_up(&config->auth_password); if (config->tls_options) { aws_tls_connection_options_clean_up(config->tls_options); aws_mem_release(config->allocator, config->tls_options); } + aws_http_proxy_strategy_release(config->proxy_strategy); + aws_mem_release(config->allocator, config); } @@ -850,10 +1088,29 @@ void aws_http_proxy_options_init_from_config( const struct aws_http_proxy_config *config) { AWS_FATAL_ASSERT(options && config); + options->connection_type = config->connection_type; options->host = aws_byte_cursor_from_buf(&config->host); - options->auth_username = aws_byte_cursor_from_buf(&config->auth_username); - options->auth_password = aws_byte_cursor_from_buf(&config->auth_password); - options->auth_type = config->auth_type; options->port = config->port; options->tls_options = config->tls_options; + options->proxy_strategy = config->proxy_strategy; +} + +int aws_http_options_validate_proxy_configuration(const struct aws_http_client_connection_options *options) { + if (options == NULL || options->proxy_options == NULL) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + enum aws_http_proxy_connection_type proxy_type = options->proxy_options->connection_type; + if (proxy_type == AWS_HPCT_HTTP_FORWARD && options->tls_options != NULL) { + return aws_raise_error(AWS_ERROR_INVALID_STATE); + } + + struct aws_http_proxy_strategy *proxy_strategy = options->proxy_options->proxy_strategy; + if (proxy_strategy != NULL) { + if (proxy_strategy->proxy_connection_type != proxy_type) { + return aws_raise_error(AWS_ERROR_INVALID_STATE); + } + } + + return AWS_OP_SUCCESS; } diff --git a/source/proxy_strategy.c b/source/proxy_strategy.c new file mode 100644 index 000000000..3130d91cc --- /dev/null +++ b/source/proxy_strategy.c @@ -0,0 +1,1703 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include + +#include +#include +#include + +#if defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4221) +#endif /* _MSC_VER */ + +struct aws_http_proxy_negotiator *aws_http_proxy_negotiator_acquire( + struct aws_http_proxy_negotiator *proxy_negotiator) { + if (proxy_negotiator != NULL) { + aws_ref_count_acquire(&proxy_negotiator->ref_count); + } + + return proxy_negotiator; +} + +void aws_http_proxy_negotiator_release(struct aws_http_proxy_negotiator *proxy_negotiator) { + if (proxy_negotiator != NULL) { + aws_ref_count_release(&proxy_negotiator->ref_count); + } +} + +struct aws_http_proxy_negotiator *aws_http_proxy_strategy_create_negotiator( + struct aws_http_proxy_strategy *strategy, + struct aws_allocator *allocator) { + if (strategy == NULL || allocator == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + return strategy->vtable->create_negotiator(strategy, allocator); +} + +enum aws_http_proxy_negotiation_retry_directive aws_http_proxy_negotiator_get_retry_directive( + struct aws_http_proxy_negotiator *proxy_negotiator) { + if (proxy_negotiator != NULL) { + if (proxy_negotiator->strategy_vtable.tunnelling_vtable->get_retry_directive != NULL) { + return proxy_negotiator->strategy_vtable.tunnelling_vtable->get_retry_directive(proxy_negotiator); + } + } + + return AWS_HPNRD_STOP; +} + +struct aws_http_proxy_strategy *aws_http_proxy_strategy_acquire(struct aws_http_proxy_strategy *proxy_strategy) { + if (proxy_strategy != NULL) { + aws_ref_count_acquire(&proxy_strategy->ref_count); + } + + return proxy_strategy; +} + +void aws_http_proxy_strategy_release(struct aws_http_proxy_strategy *proxy_strategy) { + if (proxy_strategy != NULL) { + aws_ref_count_release(&proxy_strategy->ref_count); + } +} + +/*****************************************************************************************************************/ + +enum proxy_negotiator_connect_state { + AWS_PNCS_READY, + AWS_PNCS_IN_PROGRESS, + AWS_PNCS_SUCCESS, + AWS_PNCS_FAILURE, +}; + +/* Functions for basic auth strategy */ + +struct aws_http_proxy_strategy_basic_auth { + struct aws_allocator *allocator; + struct aws_string *user_name; + struct aws_string *password; + struct aws_http_proxy_strategy strategy_base; +}; + +static void s_destroy_basic_auth_strategy(struct aws_http_proxy_strategy *proxy_strategy) { + struct aws_http_proxy_strategy_basic_auth *basic_auth_strategy = proxy_strategy->impl; + + aws_string_destroy(basic_auth_strategy->user_name); + aws_string_destroy(basic_auth_strategy->password); + + aws_mem_release(basic_auth_strategy->allocator, basic_auth_strategy); +} + +struct aws_http_proxy_negotiator_basic_auth { + struct aws_allocator *allocator; + + struct aws_http_proxy_strategy *strategy; + + enum proxy_negotiator_connect_state connect_state; + + struct aws_http_proxy_negotiator negotiator_base; +}; + +static void s_destroy_basic_auth_negotiator(struct aws_http_proxy_negotiator *proxy_negotiator) { + struct aws_http_proxy_negotiator_basic_auth *basic_auth_negotiator = proxy_negotiator->impl; + + aws_http_proxy_strategy_release(basic_auth_negotiator->strategy); + + aws_mem_release(basic_auth_negotiator->allocator, basic_auth_negotiator); +} + +AWS_STATIC_STRING_FROM_LITERAL(s_proxy_authorization_header_name, "Proxy-Authorization"); +AWS_STATIC_STRING_FROM_LITERAL(s_proxy_authorization_header_basic_prefix, "Basic "); + +/* + * Adds a proxy authentication header based on the basic authentication mode, rfc7617 + */ +static int s_add_basic_proxy_authentication_header( + struct aws_allocator *allocator, + struct aws_http_message *request, + struct aws_http_proxy_negotiator_basic_auth *basic_auth_negotiator) { + + struct aws_byte_buf base64_input_value; + AWS_ZERO_STRUCT(base64_input_value); + + struct aws_byte_buf header_value; + AWS_ZERO_STRUCT(header_value); + + int result = AWS_OP_ERR; + + struct aws_http_proxy_strategy_basic_auth *basic_auth_strategy = basic_auth_negotiator->strategy->impl; + + if (aws_byte_buf_init( + &base64_input_value, + allocator, + basic_auth_strategy->user_name->len + basic_auth_strategy->password->len + 1)) { + goto done; + } + + /* First build a buffer with "username:password" in it */ + struct aws_byte_cursor username_cursor = aws_byte_cursor_from_string(basic_auth_strategy->user_name); + if (aws_byte_buf_append(&base64_input_value, &username_cursor)) { + goto done; + } + + struct aws_byte_cursor colon_cursor = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL(":"); + if (aws_byte_buf_append(&base64_input_value, &colon_cursor)) { + goto done; + } + + struct aws_byte_cursor password_cursor = aws_byte_cursor_from_string(basic_auth_strategy->password); + if (aws_byte_buf_append(&base64_input_value, &password_cursor)) { + goto done; + } + + struct aws_byte_cursor base64_source_cursor = + aws_byte_cursor_from_array(base64_input_value.buffer, base64_input_value.len); + + /* Figure out how much room we need in our final header value buffer */ + size_t required_size = 0; + if (aws_base64_compute_encoded_len(base64_source_cursor.len, &required_size)) { + goto done; + } + + required_size += s_proxy_authorization_header_basic_prefix->len + 1; + if (aws_byte_buf_init(&header_value, allocator, required_size)) { + goto done; + } + + /* Build the final header value by appending the authorization type and the base64 encoding string together */ + struct aws_byte_cursor basic_prefix = aws_byte_cursor_from_string(s_proxy_authorization_header_basic_prefix); + if (aws_byte_buf_append_dynamic(&header_value, &basic_prefix)) { + goto done; + } + + if (aws_base64_encode(&base64_source_cursor, &header_value)) { + goto done; + } + + struct aws_http_header header = { + .name = aws_byte_cursor_from_string(s_proxy_authorization_header_name), + .value = aws_byte_cursor_from_array(header_value.buffer, header_value.len), + }; + + if (aws_http_message_add_header(request, header)) { + goto done; + } + + result = AWS_OP_SUCCESS; + +done: + + aws_byte_buf_clean_up(&header_value); + aws_byte_buf_clean_up(&base64_input_value); + + return result; +} + +int s_basic_auth_forward_add_header( + struct aws_http_proxy_negotiator *proxy_negotiator, + struct aws_http_message *message) { + struct aws_http_proxy_negotiator_basic_auth *basic_auth_negotiator = proxy_negotiator->impl; + + return s_add_basic_proxy_authentication_header(basic_auth_negotiator->allocator, message, basic_auth_negotiator); +} + +void s_basic_auth_tunnel_add_header( + struct aws_http_proxy_negotiator *proxy_negotiator, + struct aws_http_message *message, + aws_http_proxy_negotiation_terminate_fn *negotiation_termination_callback, + aws_http_proxy_negotiation_http_request_forward_fn *negotiation_http_request_forward_callback, + void *internal_proxy_user_data) { + + struct aws_http_proxy_negotiator_basic_auth *basic_auth_negotiator = proxy_negotiator->impl; + if (basic_auth_negotiator->connect_state != AWS_PNCS_READY) { + negotiation_termination_callback(message, AWS_ERROR_HTTP_PROXY_CONNECT_FAILED, internal_proxy_user_data); + return; + } + + basic_auth_negotiator->connect_state = AWS_PNCS_IN_PROGRESS; + + if (s_add_basic_proxy_authentication_header(basic_auth_negotiator->allocator, message, basic_auth_negotiator)) { + negotiation_termination_callback(message, aws_last_error(), internal_proxy_user_data); + return; + } + + negotiation_http_request_forward_callback(message, internal_proxy_user_data); +} + +static int s_basic_auth_on_connect_status( + struct aws_http_proxy_negotiator *proxy_negotiator, + enum aws_http_status_code status_code) { + struct aws_http_proxy_negotiator_basic_auth *basic_auth_negotiator = proxy_negotiator->impl; + + if (basic_auth_negotiator->connect_state == AWS_PNCS_IN_PROGRESS) { + if (AWS_HTTP_STATUS_CODE_200_OK != status_code) { + basic_auth_negotiator->connect_state = AWS_PNCS_FAILURE; + } else { + basic_auth_negotiator->connect_state = AWS_PNCS_SUCCESS; + } + } + + return AWS_OP_SUCCESS; +} + +static struct aws_http_proxy_negotiator_forwarding_vtable s_basic_auth_proxy_negotiator_forwarding_vtable = { + .forward_request_transform = s_basic_auth_forward_add_header, +}; + +static struct aws_http_proxy_negotiator_tunnelling_vtable s_basic_auth_proxy_negotiator_tunneling_vtable = { + .on_status_callback = s_basic_auth_on_connect_status, + .connect_request_transform = s_basic_auth_tunnel_add_header, +}; + +static struct aws_http_proxy_negotiator *s_create_basic_auth_negotiator( + struct aws_http_proxy_strategy *proxy_strategy, + struct aws_allocator *allocator) { + if (proxy_strategy == NULL || allocator == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_http_proxy_negotiator_basic_auth *basic_auth_negotiator = + aws_mem_calloc(allocator, 1, sizeof(struct aws_http_proxy_negotiator_basic_auth)); + if (basic_auth_negotiator == NULL) { + return NULL; + } + + basic_auth_negotiator->allocator = allocator; + basic_auth_negotiator->connect_state = AWS_PNCS_READY; + basic_auth_negotiator->negotiator_base.impl = basic_auth_negotiator; + aws_ref_count_init( + &basic_auth_negotiator->negotiator_base.ref_count, + &basic_auth_negotiator->negotiator_base, + (aws_simple_completion_callback *)s_destroy_basic_auth_negotiator); + + if (proxy_strategy->proxy_connection_type == AWS_HPCT_HTTP_FORWARD) { + basic_auth_negotiator->negotiator_base.strategy_vtable.forwarding_vtable = + &s_basic_auth_proxy_negotiator_forwarding_vtable; + } else { + basic_auth_negotiator->negotiator_base.strategy_vtable.tunnelling_vtable = + &s_basic_auth_proxy_negotiator_tunneling_vtable; + } + + basic_auth_negotiator->strategy = aws_http_proxy_strategy_acquire(proxy_strategy); + + return &basic_auth_negotiator->negotiator_base; +} + +static struct aws_http_proxy_strategy_vtable s_basic_auth_proxy_strategy_vtable = { + .create_negotiator = s_create_basic_auth_negotiator, +}; + +struct aws_http_proxy_strategy *aws_http_proxy_strategy_new_basic_auth( + struct aws_allocator *allocator, + struct aws_http_proxy_strategy_basic_auth_options *config) { + if (config == NULL || allocator == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + if (config->proxy_connection_type != AWS_HPCT_HTTP_FORWARD && + config->proxy_connection_type != AWS_HPCT_HTTP_TUNNEL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_http_proxy_strategy_basic_auth *basic_auth_strategy = + aws_mem_calloc(allocator, 1, sizeof(struct aws_http_proxy_strategy_basic_auth)); + if (basic_auth_strategy == NULL) { + return NULL; + } + + basic_auth_strategy->strategy_base.impl = basic_auth_strategy; + basic_auth_strategy->strategy_base.vtable = &s_basic_auth_proxy_strategy_vtable; + basic_auth_strategy->allocator = allocator; + basic_auth_strategy->strategy_base.proxy_connection_type = config->proxy_connection_type; + aws_ref_count_init( + &basic_auth_strategy->strategy_base.ref_count, + &basic_auth_strategy->strategy_base, + (aws_simple_completion_callback *)s_destroy_basic_auth_strategy); + + basic_auth_strategy->user_name = aws_string_new_from_cursor(allocator, &config->user_name); + if (basic_auth_strategy->user_name == NULL) { + goto on_error; + } + + basic_auth_strategy->password = aws_string_new_from_cursor(allocator, &config->password); + if (basic_auth_strategy->password == NULL) { + goto on_error; + } + + return &basic_auth_strategy->strategy_base; + +on_error: + + aws_http_proxy_strategy_release(&basic_auth_strategy->strategy_base); + + return NULL; +} + +/*****************************************************************************************************************/ + +struct aws_http_proxy_strategy_one_time_identity { + struct aws_allocator *allocator; + + struct aws_http_proxy_strategy strategy_base; +}; + +struct aws_http_proxy_negotiator_one_time_identity { + struct aws_allocator *allocator; + + enum proxy_negotiator_connect_state connect_state; + + struct aws_http_proxy_negotiator negotiator_base; +}; + +static void s_destroy_one_time_identity_negotiator(struct aws_http_proxy_negotiator *proxy_negotiator) { + struct aws_http_proxy_negotiator_one_time_identity *identity_negotiator = proxy_negotiator->impl; + + aws_mem_release(identity_negotiator->allocator, identity_negotiator); +} + +void s_one_time_identity_connect_transform( + struct aws_http_proxy_negotiator *proxy_negotiator, + struct aws_http_message *message, + aws_http_proxy_negotiation_terminate_fn *negotiation_termination_callback, + aws_http_proxy_negotiation_http_request_forward_fn *negotiation_http_request_forward_callback, + void *internal_proxy_user_data) { + + struct aws_http_proxy_negotiator_one_time_identity *one_time_identity_negotiator = proxy_negotiator->impl; + if (one_time_identity_negotiator->connect_state != AWS_PNCS_READY) { + negotiation_termination_callback(message, AWS_ERROR_HTTP_PROXY_CONNECT_FAILED, internal_proxy_user_data); + return; + } + + one_time_identity_negotiator->connect_state = AWS_PNCS_IN_PROGRESS; + negotiation_http_request_forward_callback(message, internal_proxy_user_data); +} + +static int s_one_time_identity_on_connect_status( + struct aws_http_proxy_negotiator *proxy_negotiator, + enum aws_http_status_code status_code) { + struct aws_http_proxy_negotiator_one_time_identity *one_time_identity_negotiator = proxy_negotiator->impl; + + if (one_time_identity_negotiator->connect_state == AWS_PNCS_IN_PROGRESS) { + if (AWS_HTTP_STATUS_CODE_200_OK != status_code) { + one_time_identity_negotiator->connect_state = AWS_PNCS_FAILURE; + } else { + one_time_identity_negotiator->connect_state = AWS_PNCS_SUCCESS; + } + } + + return AWS_OP_SUCCESS; +} + +static struct aws_http_proxy_negotiator_tunnelling_vtable s_one_time_identity_proxy_negotiator_tunneling_vtable = { + .on_status_callback = s_one_time_identity_on_connect_status, + .connect_request_transform = s_one_time_identity_connect_transform, +}; + +static struct aws_http_proxy_negotiator *s_create_one_time_identity_negotiator( + struct aws_http_proxy_strategy *proxy_strategy, + struct aws_allocator *allocator) { + if (proxy_strategy == NULL || allocator == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_http_proxy_negotiator_one_time_identity *identity_negotiator = + aws_mem_calloc(allocator, 1, sizeof(struct aws_http_proxy_negotiator_one_time_identity)); + if (identity_negotiator == NULL) { + return NULL; + } + + identity_negotiator->allocator = allocator; + identity_negotiator->connect_state = AWS_PNCS_READY; + identity_negotiator->negotiator_base.impl = identity_negotiator; + aws_ref_count_init( + &identity_negotiator->negotiator_base.ref_count, + &identity_negotiator->negotiator_base, + (aws_simple_completion_callback *)s_destroy_one_time_identity_negotiator); + + identity_negotiator->negotiator_base.strategy_vtable.tunnelling_vtable = + &s_one_time_identity_proxy_negotiator_tunneling_vtable; + + return &identity_negotiator->negotiator_base; +} + +static struct aws_http_proxy_strategy_vtable s_one_time_identity_proxy_strategy_vtable = { + .create_negotiator = s_create_one_time_identity_negotiator, +}; + +static void s_destroy_one_time_identity_strategy(struct aws_http_proxy_strategy *proxy_strategy) { + struct aws_http_proxy_strategy_one_time_identity *identity_strategy = proxy_strategy->impl; + + aws_mem_release(identity_strategy->allocator, identity_strategy); +} + +struct aws_http_proxy_strategy *aws_http_proxy_strategy_new_tunneling_one_time_identity( + struct aws_allocator *allocator) { + if (allocator == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_http_proxy_strategy_one_time_identity *identity_strategy = + aws_mem_calloc(allocator, 1, sizeof(struct aws_http_proxy_strategy_one_time_identity)); + if (identity_strategy == NULL) { + return NULL; + } + + identity_strategy->strategy_base.impl = identity_strategy; + identity_strategy->strategy_base.vtable = &s_one_time_identity_proxy_strategy_vtable; + identity_strategy->strategy_base.proxy_connection_type = AWS_HPCT_HTTP_TUNNEL; + identity_strategy->allocator = allocator; + + aws_ref_count_init( + &identity_strategy->strategy_base.ref_count, + &identity_strategy->strategy_base, + (aws_simple_completion_callback *)s_destroy_one_time_identity_strategy); + + return &identity_strategy->strategy_base; +} + +/******************************************************************************************************************/ + +struct aws_http_proxy_strategy_forwarding_identity { + struct aws_allocator *allocator; + + struct aws_http_proxy_strategy strategy_base; +}; + +struct aws_http_proxy_negotiator_forwarding_identity { + struct aws_allocator *allocator; + + struct aws_http_proxy_negotiator negotiator_base; +}; + +static void s_destroy_forwarding_identity_negotiator(struct aws_http_proxy_negotiator *proxy_negotiator) { + struct aws_http_proxy_negotiator_forwarding_identity *identity_negotiator = proxy_negotiator->impl; + + aws_mem_release(identity_negotiator->allocator, identity_negotiator); +} + +int s_forwarding_identity_connect_transform( + struct aws_http_proxy_negotiator *proxy_negotiator, + struct aws_http_message *message) { + + (void)message; + (void)proxy_negotiator; + + return AWS_OP_SUCCESS; +} + +static struct aws_http_proxy_negotiator_forwarding_vtable s_forwarding_identity_proxy_negotiator_tunneling_vtable = { + .forward_request_transform = s_forwarding_identity_connect_transform, +}; + +static struct aws_http_proxy_negotiator *s_create_forwarding_identity_negotiator( + struct aws_http_proxy_strategy *proxy_strategy, + struct aws_allocator *allocator) { + if (proxy_strategy == NULL || allocator == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_http_proxy_negotiator_forwarding_identity *identity_negotiator = + aws_mem_calloc(allocator, 1, sizeof(struct aws_http_proxy_negotiator_forwarding_identity)); + if (identity_negotiator == NULL) { + return NULL; + } + + identity_negotiator->allocator = allocator; + identity_negotiator->negotiator_base.impl = identity_negotiator; + aws_ref_count_init( + &identity_negotiator->negotiator_base.ref_count, + &identity_negotiator->negotiator_base, + (aws_simple_completion_callback *)s_destroy_forwarding_identity_negotiator); + + identity_negotiator->negotiator_base.strategy_vtable.forwarding_vtable = + &s_forwarding_identity_proxy_negotiator_tunneling_vtable; + + return &identity_negotiator->negotiator_base; +} + +static struct aws_http_proxy_strategy_vtable s_forwarding_identity_strategy_vtable = { + .create_negotiator = s_create_forwarding_identity_negotiator, +}; + +static void s_destroy_forwarding_identity_strategy(struct aws_http_proxy_strategy *proxy_strategy) { + struct aws_http_proxy_strategy_forwarding_identity *identity_strategy = proxy_strategy->impl; + + aws_mem_release(identity_strategy->allocator, identity_strategy); +} + +struct aws_http_proxy_strategy *aws_http_proxy_strategy_new_forwarding_identity(struct aws_allocator *allocator) { + if (allocator == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_http_proxy_strategy_forwarding_identity *identity_strategy = + aws_mem_calloc(allocator, 1, sizeof(struct aws_http_proxy_strategy_forwarding_identity)); + if (identity_strategy == NULL) { + return NULL; + } + + identity_strategy->strategy_base.impl = identity_strategy; + identity_strategy->strategy_base.vtable = &s_forwarding_identity_strategy_vtable; + identity_strategy->strategy_base.proxy_connection_type = AWS_HPCT_HTTP_FORWARD; + identity_strategy->allocator = allocator; + + aws_ref_count_init( + &identity_strategy->strategy_base.ref_count, + &identity_strategy->strategy_base, + (aws_simple_completion_callback *)s_destroy_forwarding_identity_strategy); + + return &identity_strategy->strategy_base; +} + +/******************************************************************************************************************/ +/* kerberos */ + +AWS_STATIC_STRING_FROM_LITERAL(s_proxy_authorization_header_kerberos_prefix, "Negotiate "); + +struct aws_http_proxy_strategy_tunneling_kerberos { + struct aws_allocator *allocator; + + aws_http_proxy_negotiation_get_token_sync_fn *get_token; + + void *get_token_user_data; + + struct aws_http_proxy_strategy strategy_base; +}; + +struct aws_http_proxy_negotiator_tunneling_kerberos { + struct aws_allocator *allocator; + + struct aws_http_proxy_strategy *strategy; + + enum proxy_negotiator_connect_state connect_state; + + /* + * ToDo: make adaptive and add any state needed here + * + * Likely things include response code (from the vanilla CONNECT) and the appropriate headers in + * the response + */ + + struct aws_http_proxy_negotiator negotiator_base; +}; + +/* + * Adds a proxy authentication header based on the user kerberos authentication token + * This uses a token that is already base64 encoded + */ +static int s_add_kerberos_proxy_usertoken_authentication_header( + struct aws_allocator *allocator, + struct aws_http_message *request, + struct aws_byte_cursor user_token) { + + struct aws_byte_buf header_value; + AWS_ZERO_STRUCT(header_value); + + int result = AWS_OP_ERR; + + if (aws_byte_buf_init( + &header_value, allocator, s_proxy_authorization_header_kerberos_prefix->len + user_token.len)) { + goto done; + } + + /* First append proxy authorization header kerberos prefix */ + struct aws_byte_cursor auth_header_cursor = + aws_byte_cursor_from_string(s_proxy_authorization_header_kerberos_prefix); + if (aws_byte_buf_append(&header_value, &auth_header_cursor)) { + goto done; + } + + /* Append token to it */ + if (aws_byte_buf_append(&header_value, &user_token)) { + goto done; + } + + struct aws_http_header header = { + .name = aws_byte_cursor_from_string(s_proxy_authorization_header_name), + .value = aws_byte_cursor_from_array(header_value.buffer, header_value.len), + }; + + if (aws_http_message_add_header(request, header)) { + goto done; + } + + result = AWS_OP_SUCCESS; + +done: + + aws_byte_buf_clean_up(&header_value); + return result; +} + +static void s_kerberos_tunnel_transform_connect( + struct aws_http_proxy_negotiator *proxy_negotiator, + struct aws_http_message *message, + aws_http_proxy_negotiation_terminate_fn *negotiation_termination_callback, + aws_http_proxy_negotiation_http_request_forward_fn *negotiation_http_request_forward_callback, + void *internal_proxy_user_data) { + struct aws_http_proxy_negotiator_tunneling_kerberos *kerberos_negotiator = proxy_negotiator->impl; + struct aws_http_proxy_strategy_tunneling_kerberos *kerberos_strategy = kerberos_negotiator->strategy->impl; + + int result = AWS_OP_ERR; + int error_code = AWS_ERROR_SUCCESS; + struct aws_string *kerberos_token = NULL; + + if (kerberos_negotiator->connect_state == AWS_PNCS_FAILURE) { + error_code = AWS_ERROR_HTTP_PROXY_CONNECT_FAILED; + goto done; + } + + if (kerberos_negotiator->connect_state != AWS_PNCS_READY) { + error_code = AWS_ERROR_INVALID_STATE; + goto done; + } + + kerberos_negotiator->connect_state = AWS_PNCS_IN_PROGRESS; + + kerberos_token = kerberos_strategy->get_token(kerberos_strategy->get_token_user_data, &error_code); + if (kerberos_token == NULL || error_code != AWS_ERROR_SUCCESS) { + goto done; + } + + /*transform the header with proxy authenticate:Negotiate and kerberos token*/ + if (s_add_kerberos_proxy_usertoken_authentication_header( + kerberos_negotiator->allocator, message, aws_byte_cursor_from_string(kerberos_token))) { + error_code = aws_last_error(); + goto done; + } + + kerberos_negotiator->connect_state = AWS_PNCS_IN_PROGRESS; + result = AWS_OP_SUCCESS; + +done: + + if (result != AWS_OP_SUCCESS) { + if (error_code == AWS_ERROR_SUCCESS) { + error_code = AWS_ERROR_UNKNOWN; + } + negotiation_termination_callback(message, error_code, internal_proxy_user_data); + } else { + negotiation_http_request_forward_callback(message, internal_proxy_user_data); + } + + aws_string_destroy(kerberos_token); +} + +static int s_kerberos_on_incoming_header_adaptive( + struct aws_http_proxy_negotiator *proxy_negotiator, + enum aws_http_header_block header_block, + const struct aws_http_header *header_array, + size_t num_headers) { + + struct aws_http_proxy_negotiator_tunneling_kerberos *kerberos_negotiator = proxy_negotiator->impl; + (void)kerberos_negotiator; + (void)header_block; + (void)header_array; + (void)num_headers; + + /* TODO: process vanilla CONNECT response headers here to improve usage/application */ + + return AWS_OP_SUCCESS; +} + +static int s_kerberos_on_connect_status( + struct aws_http_proxy_negotiator *proxy_negotiator, + enum aws_http_status_code status_code) { + + struct aws_http_proxy_negotiator_tunneling_kerberos *kerberos_negotiator = proxy_negotiator->impl; + + /* TODO: process status code of vanilla CONNECT request here to improve usage/application */ + + if (kerberos_negotiator->connect_state == AWS_PNCS_IN_PROGRESS) { + if (AWS_HTTP_STATUS_CODE_200_OK != status_code) { + kerberos_negotiator->connect_state = AWS_PNCS_FAILURE; + } else { + kerberos_negotiator->connect_state = AWS_PNCS_SUCCESS; + } + } + + return AWS_OP_SUCCESS; +} + +static int s_kerberos_on_incoming_body( + struct aws_http_proxy_negotiator *proxy_negotiator, + const struct aws_byte_cursor *data) { + + struct aws_http_proxy_negotiator_tunneling_kerberos *kerberos_negotiator = proxy_negotiator->impl; + (void)kerberos_negotiator; + (void)data; + + return AWS_OP_SUCCESS; +} + +static struct aws_http_proxy_negotiator_tunnelling_vtable s_tunneling_kerberos_proxy_negotiator_tunneling_vtable = { + .on_incoming_body_callback = s_kerberos_on_incoming_body, + .on_incoming_headers_callback = s_kerberos_on_incoming_header_adaptive, + .on_status_callback = s_kerberos_on_connect_status, + .connect_request_transform = s_kerberos_tunnel_transform_connect, +}; + +static void s_destroy_tunneling_kerberos_negotiator(struct aws_http_proxy_negotiator *proxy_negotiator) { + struct aws_http_proxy_negotiator_tunneling_kerberos *kerberos_negotiator = proxy_negotiator->impl; + + aws_http_proxy_strategy_release(kerberos_negotiator->strategy); + + aws_mem_release(kerberos_negotiator->allocator, kerberos_negotiator); +} + +static struct aws_http_proxy_negotiator *s_create_tunneling_kerberos_negotiator( + struct aws_http_proxy_strategy *proxy_strategy, + struct aws_allocator *allocator) { + if (proxy_strategy == NULL || allocator == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_http_proxy_negotiator_tunneling_kerberos *kerberos_negotiator = + aws_mem_calloc(allocator, 1, sizeof(struct aws_http_proxy_negotiator_tunneling_kerberos)); + if (kerberos_negotiator == NULL) { + return NULL; + } + + kerberos_negotiator->allocator = allocator; + kerberos_negotiator->negotiator_base.impl = kerberos_negotiator; + aws_ref_count_init( + &kerberos_negotiator->negotiator_base.ref_count, + &kerberos_negotiator->negotiator_base, + (aws_simple_completion_callback *)s_destroy_tunneling_kerberos_negotiator); + + kerberos_negotiator->negotiator_base.strategy_vtable.tunnelling_vtable = + &s_tunneling_kerberos_proxy_negotiator_tunneling_vtable; + + kerberos_negotiator->strategy = aws_http_proxy_strategy_acquire(proxy_strategy); + + return &kerberos_negotiator->negotiator_base; +} + +static struct aws_http_proxy_strategy_vtable s_tunneling_kerberos_strategy_vtable = { + .create_negotiator = s_create_tunneling_kerberos_negotiator, +}; + +static void s_destroy_tunneling_kerberos_strategy(struct aws_http_proxy_strategy *proxy_strategy) { + struct aws_http_proxy_strategy_tunneling_kerberos *kerberos_strategy = proxy_strategy->impl; + + aws_mem_release(kerberos_strategy->allocator, kerberos_strategy); +} + +struct aws_http_proxy_strategy *aws_http_proxy_strategy_new_tunneling_kerberos( + struct aws_allocator *allocator, + struct aws_http_proxy_strategy_tunneling_kerberos_options *config) { + + if (allocator == NULL || config == NULL || config->get_token == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_http_proxy_strategy_tunneling_kerberos *kerberos_strategy = + aws_mem_calloc(allocator, 1, sizeof(struct aws_http_proxy_strategy_tunneling_kerberos)); + if (kerberos_strategy == NULL) { + return NULL; + } + + kerberos_strategy->strategy_base.impl = kerberos_strategy; + kerberos_strategy->strategy_base.vtable = &s_tunneling_kerberos_strategy_vtable; + kerberos_strategy->strategy_base.proxy_connection_type = AWS_HPCT_HTTP_TUNNEL; + kerberos_strategy->allocator = allocator; + + aws_ref_count_init( + &kerberos_strategy->strategy_base.ref_count, + &kerberos_strategy->strategy_base, + (aws_simple_completion_callback *)s_destroy_tunneling_kerberos_strategy); + + kerberos_strategy->get_token = config->get_token; + kerberos_strategy->get_token_user_data = config->get_token_user_data; + + return &kerberos_strategy->strategy_base; +} + +/******************************************************************************************************************/ + +struct aws_http_proxy_strategy_tunneling_ntlm { + struct aws_allocator *allocator; + + aws_http_proxy_negotiation_get_token_sync_fn *get_token; + + aws_http_proxy_negotiation_get_challenge_token_sync_fn *get_challenge_token; + + void *get_challenge_token_user_data; + + struct aws_http_proxy_strategy strategy_base; +}; + +struct aws_http_proxy_negotiator_tunneling_ntlm { + struct aws_allocator *allocator; + + struct aws_http_proxy_strategy *strategy; + + enum proxy_negotiator_connect_state connect_state; + + struct aws_string *challenge_token; + + struct aws_http_proxy_negotiator negotiator_base; +}; + +AWS_STATIC_STRING_FROM_LITERAL(s_proxy_authorization_header_ntlm_prefix, "NTLM "); + +/* + * Adds a proxy authentication header based on ntlm credential or response provided by user + */ +static int s_add_ntlm_proxy_usertoken_authentication_header( + struct aws_allocator *allocator, + struct aws_http_message *request, + struct aws_byte_cursor credential_response) { + + struct aws_byte_buf header_value; + AWS_ZERO_STRUCT(header_value); + + int result = AWS_OP_ERR; + + if (aws_byte_buf_init( + &header_value, allocator, s_proxy_authorization_header_ntlm_prefix->len + credential_response.len)) { + goto done; + } + + /* First append proxy authorization header prefix */ + struct aws_byte_cursor auth_header_cursor = aws_byte_cursor_from_string(s_proxy_authorization_header_ntlm_prefix); + if (aws_byte_buf_append(&header_value, &auth_header_cursor)) { + goto done; + } + + /* Append the credential response to it; assumes already encoded properly (base64) */ + if (aws_byte_buf_append(&header_value, &credential_response)) { + goto done; + } + + struct aws_http_header header = { + .name = aws_byte_cursor_from_string(s_proxy_authorization_header_name), + .value = aws_byte_cursor_from_array(header_value.buffer, header_value.len), + }; + + if (aws_http_message_add_header(request, header)) { + goto done; + } + + result = AWS_OP_SUCCESS; + +done: + + aws_byte_buf_clean_up(&header_value); + return result; +} + +static void s_ntlm_tunnel_transform_connect( + struct aws_http_proxy_negotiator *proxy_negotiator, + struct aws_http_message *message, + aws_http_proxy_negotiation_terminate_fn *negotiation_termination_callback, + aws_http_proxy_negotiation_http_request_forward_fn *negotiation_http_request_forward_callback, + void *internal_proxy_user_data) { + + struct aws_http_proxy_negotiator_tunneling_ntlm *ntlm_negotiator = proxy_negotiator->impl; + struct aws_http_proxy_strategy_tunneling_ntlm *ntlm_strategy = ntlm_negotiator->strategy->impl; + + int result = AWS_OP_ERR; + int error_code = AWS_ERROR_SUCCESS; + struct aws_string *challenge_answer_token = NULL; + struct aws_byte_cursor challenge_token; + AWS_ZERO_STRUCT(challenge_token); + + if (ntlm_negotiator->connect_state == AWS_PNCS_FAILURE) { + error_code = AWS_ERROR_HTTP_PROXY_CONNECT_FAILED; + goto done; + } + + if (ntlm_negotiator->connect_state != AWS_PNCS_READY) { + error_code = AWS_ERROR_INVALID_STATE; + goto done; + } + + if (ntlm_negotiator->challenge_token == NULL) { + error_code = AWS_ERROR_HTTP_PROXY_STRATEGY_NTLM_CHALLENGE_TOKEN_MISSING; + goto done; + } + + ntlm_negotiator->connect_state = AWS_PNCS_IN_PROGRESS; + challenge_token = aws_byte_cursor_from_string(ntlm_negotiator->challenge_token); + challenge_answer_token = + ntlm_strategy->get_challenge_token(ntlm_strategy->get_challenge_token_user_data, &challenge_token, &error_code); + + if (challenge_answer_token == NULL || error_code != AWS_ERROR_SUCCESS) { + goto done; + } + + /*transform the header with proxy authenticate:Negotiate and kerberos token*/ + if (s_add_ntlm_proxy_usertoken_authentication_header( + ntlm_negotiator->allocator, message, aws_byte_cursor_from_string(challenge_answer_token))) { + error_code = aws_last_error(); + goto done; + } + + ntlm_negotiator->connect_state = AWS_PNCS_IN_PROGRESS; + result = AWS_OP_SUCCESS; + +done: + + if (result != AWS_OP_SUCCESS) { + if (error_code == AWS_ERROR_SUCCESS) { + error_code = AWS_ERROR_UNKNOWN; + } + negotiation_termination_callback(message, error_code, internal_proxy_user_data); + } else { + negotiation_http_request_forward_callback(message, internal_proxy_user_data); + } + + aws_string_destroy(challenge_answer_token); +} + +AWS_STATIC_STRING_FROM_LITERAL(s_ntlm_challenge_token_header, "Proxy-Authenticate"); + +static int s_ntlm_on_incoming_header_adaptive( + struct aws_http_proxy_negotiator *proxy_negotiator, + enum aws_http_header_block header_block, + const struct aws_http_header *header_array, + size_t num_headers) { + + struct aws_http_proxy_negotiator_tunneling_ntlm *ntlm_negotiator = proxy_negotiator->impl; + + /* + * only extract the challenge before we've started our own CONNECT attempt + * + * ToDo: we currently overwrite previous challenge tokens since it is unknown if multiple CONNECT requests + * cause new challenges to be issued such that old challenges become invalid even if successfully computed + */ + if (ntlm_negotiator->connect_state == AWS_PNCS_READY) { + if (header_block == AWS_HTTP_HEADER_BLOCK_MAIN) { + struct aws_byte_cursor proxy_authenticate_header_name = + aws_byte_cursor_from_string(s_ntlm_challenge_token_header); + for (size_t i = 0; i < num_headers; ++i) { + struct aws_byte_cursor header_name_cursor = header_array[i].name; + if (aws_byte_cursor_eq_ignore_case(&proxy_authenticate_header_name, &header_name_cursor)) { + aws_string_destroy(ntlm_negotiator->challenge_token); + + struct aws_byte_cursor challenge_value_cursor = header_array[i].value; + ntlm_negotiator->challenge_token = + aws_string_new_from_cursor(ntlm_negotiator->allocator, &challenge_value_cursor); + break; + } + } + } + } + + return AWS_OP_SUCCESS; +} + +static int s_ntlm_on_connect_status( + struct aws_http_proxy_negotiator *proxy_negotiator, + enum aws_http_status_code status_code) { + + struct aws_http_proxy_negotiator_tunneling_ntlm *ntlm_negotiator = proxy_negotiator->impl; + + if (ntlm_negotiator->connect_state == AWS_PNCS_IN_PROGRESS) { + if (AWS_HTTP_STATUS_CODE_200_OK != status_code) { + ntlm_negotiator->connect_state = AWS_PNCS_FAILURE; + } else { + ntlm_negotiator->connect_state = AWS_PNCS_SUCCESS; + } + } + + return AWS_OP_SUCCESS; +} + +static int s_ntlm_on_incoming_body( + struct aws_http_proxy_negotiator *proxy_negotiator, + const struct aws_byte_cursor *data) { + + struct aws_http_proxy_negotiator_tunneling_ntlm *ntlm_negotiator = proxy_negotiator->impl; + (void)ntlm_negotiator; + (void)data; + + return AWS_OP_SUCCESS; +} + +static enum aws_http_proxy_negotiation_retry_directive s_ntlm_tunnel_get_retry_directive( + struct aws_http_proxy_negotiator *proxy_negotiator) { + (void)proxy_negotiator; + + return AWS_HPNRD_CURRENT_CONNECTION; +} + +static struct aws_http_proxy_negotiator_tunnelling_vtable s_tunneling_ntlm_proxy_negotiator_tunneling_vtable = { + .on_incoming_body_callback = s_ntlm_on_incoming_body, + .on_incoming_headers_callback = s_ntlm_on_incoming_header_adaptive, + .on_status_callback = s_ntlm_on_connect_status, + .connect_request_transform = s_ntlm_tunnel_transform_connect, + .get_retry_directive = s_ntlm_tunnel_get_retry_directive, +}; + +static void s_destroy_tunneling_ntlm_negotiator(struct aws_http_proxy_negotiator *proxy_negotiator) { + struct aws_http_proxy_negotiator_tunneling_ntlm *ntlm_negotiator = proxy_negotiator->impl; + + aws_string_destroy(ntlm_negotiator->challenge_token); + aws_http_proxy_strategy_release(ntlm_negotiator->strategy); + + aws_mem_release(ntlm_negotiator->allocator, ntlm_negotiator); +} + +static struct aws_http_proxy_negotiator *s_create_tunneling_ntlm_negotiator( + struct aws_http_proxy_strategy *proxy_strategy, + struct aws_allocator *allocator) { + if (proxy_strategy == NULL || allocator == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_http_proxy_negotiator_tunneling_ntlm *ntlm_negotiator = + aws_mem_calloc(allocator, 1, sizeof(struct aws_http_proxy_negotiator_tunneling_ntlm)); + if (ntlm_negotiator == NULL) { + return NULL; + } + + ntlm_negotiator->allocator = allocator; + ntlm_negotiator->negotiator_base.impl = ntlm_negotiator; + aws_ref_count_init( + &ntlm_negotiator->negotiator_base.ref_count, + &ntlm_negotiator->negotiator_base, + (aws_simple_completion_callback *)s_destroy_tunneling_ntlm_negotiator); + + ntlm_negotiator->negotiator_base.strategy_vtable.tunnelling_vtable = + &s_tunneling_ntlm_proxy_negotiator_tunneling_vtable; + + ntlm_negotiator->strategy = aws_http_proxy_strategy_acquire(proxy_strategy); + + return &ntlm_negotiator->negotiator_base; +} + +static struct aws_http_proxy_strategy_vtable s_tunneling_ntlm_strategy_vtable = { + .create_negotiator = s_create_tunneling_ntlm_negotiator, +}; + +static void s_destroy_tunneling_ntlm_strategy(struct aws_http_proxy_strategy *proxy_strategy) { + struct aws_http_proxy_strategy_tunneling_ntlm *ntlm_strategy = proxy_strategy->impl; + + aws_mem_release(ntlm_strategy->allocator, ntlm_strategy); +} + +struct aws_http_proxy_strategy *aws_http_proxy_strategy_new_tunneling_ntlm( + struct aws_allocator *allocator, + struct aws_http_proxy_strategy_tunneling_ntlm_options *config) { + + if (allocator == NULL || config == NULL || config->get_challenge_token == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_http_proxy_strategy_tunneling_ntlm *ntlm_strategy = + aws_mem_calloc(allocator, 1, sizeof(struct aws_http_proxy_strategy_tunneling_ntlm)); + if (ntlm_strategy == NULL) { + return NULL; + } + + ntlm_strategy->strategy_base.impl = ntlm_strategy; + ntlm_strategy->strategy_base.vtable = &s_tunneling_ntlm_strategy_vtable; + ntlm_strategy->strategy_base.proxy_connection_type = AWS_HPCT_HTTP_TUNNEL; + + ntlm_strategy->allocator = allocator; + + aws_ref_count_init( + &ntlm_strategy->strategy_base.ref_count, + &ntlm_strategy->strategy_base, + (aws_simple_completion_callback *)s_destroy_tunneling_ntlm_strategy); + + ntlm_strategy->get_challenge_token = config->get_challenge_token; + ntlm_strategy->get_challenge_token_user_data = config->get_challenge_token_user_data; + + return &ntlm_strategy->strategy_base; +} +/******************************************************************************************************/ + +static void s_ntlm_credential_tunnel_transform_connect( + struct aws_http_proxy_negotiator *proxy_negotiator, + struct aws_http_message *message, + aws_http_proxy_negotiation_terminate_fn *negotiation_termination_callback, + aws_http_proxy_negotiation_http_request_forward_fn *negotiation_http_request_forward_callback, + void *internal_proxy_user_data) { + + struct aws_http_proxy_negotiator_tunneling_ntlm *ntlm_credential_negotiator = proxy_negotiator->impl; + struct aws_http_proxy_strategy_tunneling_ntlm *ntlm_credential_strategy = + ntlm_credential_negotiator->strategy->impl; + + int result = AWS_OP_ERR; + int error_code = AWS_ERROR_SUCCESS; + struct aws_string *token = NULL; + + if (ntlm_credential_negotiator->connect_state == AWS_PNCS_FAILURE) { + error_code = AWS_ERROR_HTTP_PROXY_CONNECT_FAILED; + goto done; + } + + if (ntlm_credential_negotiator->connect_state != AWS_PNCS_READY) { + error_code = AWS_ERROR_INVALID_STATE; + goto done; + } + + ntlm_credential_negotiator->connect_state = AWS_PNCS_IN_PROGRESS; + token = ntlm_credential_strategy->get_token(ntlm_credential_strategy->get_challenge_token_user_data, &error_code); + + if (token == NULL || error_code != AWS_ERROR_SUCCESS) { + goto done; + } + + /*transform the header with proxy authenticate:Negotiate and kerberos token*/ + if (s_add_ntlm_proxy_usertoken_authentication_header( + ntlm_credential_negotiator->allocator, message, aws_byte_cursor_from_string(token))) { + error_code = aws_last_error(); + goto done; + } + + ntlm_credential_negotiator->connect_state = AWS_PNCS_IN_PROGRESS; + result = AWS_OP_SUCCESS; + +done: + + if (result != AWS_OP_SUCCESS) { + if (error_code == AWS_ERROR_SUCCESS) { + error_code = AWS_ERROR_UNKNOWN; + } + negotiation_termination_callback(message, error_code, internal_proxy_user_data); + } else { + negotiation_http_request_forward_callback(message, internal_proxy_user_data); + } + + aws_string_destroy(token); +} + +static struct aws_http_proxy_negotiator_tunnelling_vtable + s_tunneling_ntlm_proxy_credential_negotiator_tunneling_vtable = { + .on_incoming_body_callback = s_ntlm_on_incoming_body, + .on_incoming_headers_callback = s_ntlm_on_incoming_header_adaptive, + .on_status_callback = s_ntlm_on_connect_status, + .connect_request_transform = s_ntlm_credential_tunnel_transform_connect, +}; + +static void s_destroy_tunneling_ntlm_credential_negotiator(struct aws_http_proxy_negotiator *proxy_negotiator) { + struct aws_http_proxy_negotiator_tunneling_ntlm *ntlm_credential_negotiator = proxy_negotiator->impl; + + aws_string_destroy(ntlm_credential_negotiator->challenge_token); + aws_http_proxy_strategy_release(ntlm_credential_negotiator->strategy); + + aws_mem_release(ntlm_credential_negotiator->allocator, ntlm_credential_negotiator); +} + +static struct aws_http_proxy_negotiator *s_create_tunneling_ntlm_credential_negotiator( + struct aws_http_proxy_strategy *proxy_strategy, + struct aws_allocator *allocator) { + if (proxy_strategy == NULL || allocator == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_http_proxy_negotiator_tunneling_ntlm *ntlm_credential_negotiator = + aws_mem_calloc(allocator, 1, sizeof(struct aws_http_proxy_negotiator_tunneling_ntlm)); + if (ntlm_credential_negotiator == NULL) { + return NULL; + } + + ntlm_credential_negotiator->allocator = allocator; + ntlm_credential_negotiator->negotiator_base.impl = ntlm_credential_negotiator; + aws_ref_count_init( + &ntlm_credential_negotiator->negotiator_base.ref_count, + &ntlm_credential_negotiator->negotiator_base, + (aws_simple_completion_callback *)s_destroy_tunneling_ntlm_credential_negotiator); + + ntlm_credential_negotiator->negotiator_base.strategy_vtable.tunnelling_vtable = + &s_tunneling_ntlm_proxy_credential_negotiator_tunneling_vtable; + + ntlm_credential_negotiator->strategy = aws_http_proxy_strategy_acquire(proxy_strategy); + + return &ntlm_credential_negotiator->negotiator_base; +} + +static struct aws_http_proxy_strategy_vtable s_tunneling_ntlm_credential_strategy_vtable = { + .create_negotiator = s_create_tunneling_ntlm_credential_negotiator, +}; + +static void s_destroy_tunneling_ntlm_credential_strategy(struct aws_http_proxy_strategy *proxy_strategy) { + struct aws_http_proxy_strategy_tunneling_ntlm *ntlm_credential_strategy = proxy_strategy->impl; + + aws_mem_release(ntlm_credential_strategy->allocator, ntlm_credential_strategy); +} + +struct aws_http_proxy_strategy *aws_http_proxy_strategy_new_tunneling_ntlm_credential( + struct aws_allocator *allocator, + struct aws_http_proxy_strategy_tunneling_ntlm_options *config) { + + if (allocator == NULL || config == NULL || config->get_token == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_http_proxy_strategy_tunneling_ntlm *ntlm_credential_strategy = + aws_mem_calloc(allocator, 1, sizeof(struct aws_http_proxy_strategy_tunneling_ntlm)); + if (ntlm_credential_strategy == NULL) { + return NULL; + } + + ntlm_credential_strategy->strategy_base.impl = ntlm_credential_strategy; + ntlm_credential_strategy->strategy_base.vtable = &s_tunneling_ntlm_credential_strategy_vtable; + ntlm_credential_strategy->strategy_base.proxy_connection_type = AWS_HPCT_HTTP_TUNNEL; + + ntlm_credential_strategy->allocator = allocator; + + aws_ref_count_init( + &ntlm_credential_strategy->strategy_base.ref_count, + &ntlm_credential_strategy->strategy_base, + (aws_simple_completion_callback *)s_destroy_tunneling_ntlm_credential_strategy); + + ntlm_credential_strategy->get_token = config->get_token; + ntlm_credential_strategy->get_challenge_token_user_data = config->get_challenge_token_user_data; + + return &ntlm_credential_strategy->strategy_base; +} + +/******************************************************************************************************************/ + +#define PROXY_STRATEGY_MAX_ADAPTIVE_STRATEGIES 4 + +struct aws_http_proxy_strategy *aws_http_proxy_strategy_new_tunneling_adaptive( + struct aws_allocator *allocator, + struct aws_http_proxy_strategy_tunneling_adaptive_options *config) { + + if (allocator == NULL || config == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_http_proxy_strategy *strategies[PROXY_STRATEGY_MAX_ADAPTIVE_STRATEGIES]; + + uint32_t strategy_count = 0; + struct aws_http_proxy_strategy *identity_strategy = NULL; + struct aws_http_proxy_strategy *kerberos_strategy = NULL; + struct aws_http_proxy_strategy *ntlm_credential_strategy = NULL; + struct aws_http_proxy_strategy *ntlm_strategy = NULL; + struct aws_http_proxy_strategy *adaptive_sequence_strategy = NULL; + + identity_strategy = aws_http_proxy_strategy_new_tunneling_one_time_identity(allocator); + if (identity_strategy == NULL) { + goto done; + } + strategies[strategy_count++] = identity_strategy; + + if (config->kerberos_options != NULL) { + kerberos_strategy = aws_http_proxy_strategy_new_tunneling_kerberos(allocator, config->kerberos_options); + if (kerberos_strategy == NULL) { + goto done; + } + + strategies[strategy_count++] = kerberos_strategy; + } + + if (config->ntlm_options != NULL) { + ntlm_credential_strategy = + aws_http_proxy_strategy_new_tunneling_ntlm_credential(allocator, config->ntlm_options); + if (ntlm_credential_strategy == NULL) { + goto done; + } + + strategies[strategy_count++] = ntlm_credential_strategy; + + ntlm_strategy = aws_http_proxy_strategy_new_tunneling_ntlm(allocator, config->ntlm_options); + if (ntlm_strategy == NULL) { + goto done; + } + + strategies[strategy_count++] = ntlm_strategy; + } + + AWS_FATAL_ASSERT(strategy_count <= PROXY_STRATEGY_MAX_ADAPTIVE_STRATEGIES); + + struct aws_http_proxy_strategy_tunneling_sequence_options sequence_config = { + .strategies = strategies, + .strategy_count = strategy_count, + }; + + adaptive_sequence_strategy = aws_http_proxy_strategy_new_tunneling_sequence(allocator, &sequence_config); + if (adaptive_sequence_strategy == NULL) { + goto done; + } + +done: + + aws_http_proxy_strategy_release(identity_strategy); + aws_http_proxy_strategy_release(kerberos_strategy); + aws_http_proxy_strategy_release(ntlm_credential_strategy); + aws_http_proxy_strategy_release(ntlm_strategy); + + return adaptive_sequence_strategy; +} + +/******************************************************************************************************************/ + +struct aws_http_proxy_strategy_tunneling_sequence { + struct aws_allocator *allocator; + + struct aws_array_list strategies; + + struct aws_http_proxy_strategy strategy_base; +}; + +struct aws_http_proxy_negotiator_tunneling_sequence { + struct aws_allocator *allocator; + + struct aws_array_list negotiators; + size_t current_negotiator_transform_index; + void *original_internal_proxy_user_data; + aws_http_proxy_negotiation_terminate_fn *original_negotiation_termination_callback; + aws_http_proxy_negotiation_http_request_forward_fn *original_negotiation_http_request_forward_callback; + + struct aws_http_proxy_negotiator negotiator_base; +}; + +static void s_sequence_tunnel_iteration_termination_callback( + struct aws_http_message *message, + int error_code, + void *user_data) { + + struct aws_http_proxy_negotiator *proxy_negotiator = user_data; + struct aws_http_proxy_negotiator_tunneling_sequence *sequence_negotiator = proxy_negotiator->impl; + + AWS_LOGF_WARN( + AWS_LS_HTTP_PROXY_NEGOTIATION, + "(id=%p) Proxy negotiation step failed with error %d", + (void *)proxy_negotiator, + error_code); + + int connection_error_code = AWS_ERROR_HTTP_PROXY_CONNECT_FAILED_RETRYABLE; + if (sequence_negotiator->current_negotiator_transform_index >= + aws_array_list_length(&sequence_negotiator->negotiators)) { + connection_error_code = AWS_ERROR_HTTP_PROXY_CONNECT_FAILED; + } + + sequence_negotiator->original_negotiation_termination_callback( + message, connection_error_code, sequence_negotiator->original_internal_proxy_user_data); +} + +static void s_sequence_tunnel_iteration_forward_callback(struct aws_http_message *message, void *user_data) { + struct aws_http_proxy_negotiator *proxy_negotiator = user_data; + struct aws_http_proxy_negotiator_tunneling_sequence *sequence_negotiator = proxy_negotiator->impl; + + sequence_negotiator->original_negotiation_http_request_forward_callback( + message, sequence_negotiator->original_internal_proxy_user_data); +} + +static void s_sequence_tunnel_try_next_negotiator( + struct aws_http_proxy_negotiator *proxy_negotiator, + struct aws_http_message *message) { + struct aws_http_proxy_negotiator_tunneling_sequence *sequence_negotiator = proxy_negotiator->impl; + + size_t negotiator_count = aws_array_list_length(&sequence_negotiator->negotiators); + if (sequence_negotiator->current_negotiator_transform_index >= negotiator_count) { + goto on_error; + } + + struct aws_http_proxy_negotiator *current_negotiator = NULL; + if (aws_array_list_get_at( + &sequence_negotiator->negotiators, + ¤t_negotiator, + sequence_negotiator->current_negotiator_transform_index++)) { + goto on_error; + } + + current_negotiator->strategy_vtable.tunnelling_vtable->connect_request_transform( + current_negotiator, + message, + s_sequence_tunnel_iteration_termination_callback, + s_sequence_tunnel_iteration_forward_callback, + proxy_negotiator); + + return; + +on_error: + + sequence_negotiator->original_negotiation_termination_callback( + message, AWS_ERROR_HTTP_PROXY_CONNECT_FAILED, sequence_negotiator->original_internal_proxy_user_data); +} + +static void s_sequence_tunnel_transform_connect( + struct aws_http_proxy_negotiator *proxy_negotiator, + struct aws_http_message *message, + aws_http_proxy_negotiation_terminate_fn *negotiation_termination_callback, + aws_http_proxy_negotiation_http_request_forward_fn *negotiation_http_request_forward_callback, + void *internal_proxy_user_data) { + + struct aws_http_proxy_negotiator_tunneling_sequence *sequence_negotiator = proxy_negotiator->impl; + + sequence_negotiator->original_internal_proxy_user_data = internal_proxy_user_data; + sequence_negotiator->original_negotiation_termination_callback = negotiation_termination_callback; + sequence_negotiator->original_negotiation_http_request_forward_callback = negotiation_http_request_forward_callback; + + s_sequence_tunnel_try_next_negotiator(proxy_negotiator, message); +} + +static int s_sequence_on_incoming_headers( + struct aws_http_proxy_negotiator *proxy_negotiator, + enum aws_http_header_block header_block, + const struct aws_http_header *header_array, + size_t num_headers) { + + struct aws_http_proxy_negotiator_tunneling_sequence *sequence_negotiator = proxy_negotiator->impl; + size_t negotiator_count = aws_array_list_length(&sequence_negotiator->negotiators); + for (size_t i = 0; i < negotiator_count; ++i) { + struct aws_http_proxy_negotiator *negotiator = NULL; + if (aws_array_list_get_at(&sequence_negotiator->negotiators, &negotiator, i)) { + continue; + } + + aws_http_proxy_negotiation_connect_on_incoming_headers_fn *on_incoming_headers = + negotiator->strategy_vtable.tunnelling_vtable->on_incoming_headers_callback; + if (on_incoming_headers != NULL) { + (*on_incoming_headers)(negotiator, header_block, header_array, num_headers); + } + } + + return AWS_OP_SUCCESS; +} + +static int s_sequence_on_connect_status( + struct aws_http_proxy_negotiator *proxy_negotiator, + enum aws_http_status_code status_code) { + + struct aws_http_proxy_negotiator_tunneling_sequence *sequence_negotiator = proxy_negotiator->impl; + size_t negotiator_count = aws_array_list_length(&sequence_negotiator->negotiators); + for (size_t i = 0; i < negotiator_count; ++i) { + struct aws_http_proxy_negotiator *negotiator = NULL; + if (aws_array_list_get_at(&sequence_negotiator->negotiators, &negotiator, i)) { + continue; + } + + aws_http_proxy_negotiator_connect_status_fn *on_status = + negotiator->strategy_vtable.tunnelling_vtable->on_status_callback; + if (on_status != NULL) { + (*on_status)(negotiator, status_code); + } + } + + return AWS_OP_SUCCESS; +} + +static int s_sequence_on_incoming_body( + struct aws_http_proxy_negotiator *proxy_negotiator, + const struct aws_byte_cursor *data) { + + struct aws_http_proxy_negotiator_tunneling_sequence *sequence_negotiator = proxy_negotiator->impl; + size_t negotiator_count = aws_array_list_length(&sequence_negotiator->negotiators); + for (size_t i = 0; i < negotiator_count; ++i) { + struct aws_http_proxy_negotiator *negotiator = NULL; + if (aws_array_list_get_at(&sequence_negotiator->negotiators, &negotiator, i)) { + continue; + } + + aws_http_proxy_negotiator_connect_on_incoming_body_fn *on_incoming_body = + negotiator->strategy_vtable.tunnelling_vtable->on_incoming_body_callback; + if (on_incoming_body != NULL) { + (*on_incoming_body)(negotiator, data); + } + } + + return AWS_OP_SUCCESS; +} + +static enum aws_http_proxy_negotiation_retry_directive s_sequence_get_retry_directive( + struct aws_http_proxy_negotiator *proxy_negotiator) { + struct aws_http_proxy_negotiator_tunneling_sequence *sequence_negotiator = proxy_negotiator->impl; + + if (sequence_negotiator->current_negotiator_transform_index < + aws_array_list_length(&sequence_negotiator->negotiators)) { + struct aws_http_proxy_negotiator *next_negotiator = NULL; + aws_array_list_get_at( + &sequence_negotiator->negotiators, + &next_negotiator, + sequence_negotiator->current_negotiator_transform_index); + + enum aws_http_proxy_negotiation_retry_directive next_negotiator_directive = + aws_http_proxy_negotiator_get_retry_directive(next_negotiator); + if (next_negotiator_directive == AWS_HPNRD_CURRENT_CONNECTION) { + return AWS_HPNRD_CURRENT_CONNECTION; + } else { + return AWS_HPNRD_NEW_CONNECTION; + } + } + + return AWS_HPNRD_STOP; +} + +static struct aws_http_proxy_negotiator_tunnelling_vtable s_tunneling_sequence_proxy_negotiator_tunneling_vtable = { + .on_incoming_body_callback = s_sequence_on_incoming_body, + .on_incoming_headers_callback = s_sequence_on_incoming_headers, + .on_status_callback = s_sequence_on_connect_status, + .connect_request_transform = s_sequence_tunnel_transform_connect, + .get_retry_directive = s_sequence_get_retry_directive, +}; + +static void s_destroy_tunneling_sequence_negotiator(struct aws_http_proxy_negotiator *proxy_negotiator) { + struct aws_http_proxy_negotiator_tunneling_sequence *sequence_negotiator = proxy_negotiator->impl; + + size_t negotiator_count = aws_array_list_length(&sequence_negotiator->negotiators); + for (size_t i = 0; i < negotiator_count; ++i) { + struct aws_http_proxy_negotiator *negotiator = NULL; + if (aws_array_list_get_at(&sequence_negotiator->negotiators, &negotiator, i)) { + continue; + } + + aws_http_proxy_negotiator_release(negotiator); + } + + aws_array_list_clean_up(&sequence_negotiator->negotiators); + + aws_mem_release(sequence_negotiator->allocator, sequence_negotiator); +} + +static struct aws_http_proxy_negotiator *s_create_tunneling_sequence_negotiator( + struct aws_http_proxy_strategy *proxy_strategy, + struct aws_allocator *allocator) { + if (proxy_strategy == NULL || allocator == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_http_proxy_negotiator_tunneling_sequence *sequence_negotiator = + aws_mem_calloc(allocator, 1, sizeof(struct aws_http_proxy_negotiator_tunneling_sequence)); + if (sequence_negotiator == NULL) { + return NULL; + } + + sequence_negotiator->allocator = allocator; + sequence_negotiator->negotiator_base.impl = sequence_negotiator; + aws_ref_count_init( + &sequence_negotiator->negotiator_base.ref_count, + &sequence_negotiator->negotiator_base, + (aws_simple_completion_callback *)s_destroy_tunneling_sequence_negotiator); + + sequence_negotiator->negotiator_base.strategy_vtable.tunnelling_vtable = + &s_tunneling_sequence_proxy_negotiator_tunneling_vtable; + + struct aws_http_proxy_strategy_tunneling_sequence *sequence_strategy = proxy_strategy->impl; + size_t strategy_count = aws_array_list_length(&sequence_strategy->strategies); + + if (aws_array_list_init_dynamic( + &sequence_negotiator->negotiators, allocator, strategy_count, sizeof(struct aws_http_proxy_negotiator *))) { + goto on_error; + } + + for (size_t i = 0; i < strategy_count; ++i) { + struct aws_http_proxy_strategy *strategy = NULL; + if (aws_array_list_get_at(&sequence_strategy->strategies, &strategy, i)) { + goto on_error; + } + + struct aws_http_proxy_negotiator *negotiator = aws_http_proxy_strategy_create_negotiator(strategy, allocator); + if (negotiator == NULL) { + goto on_error; + } + + if (aws_array_list_push_back(&sequence_negotiator->negotiators, &negotiator)) { + aws_http_proxy_negotiator_release(negotiator); + goto on_error; + } + } + + return &sequence_negotiator->negotiator_base; + +on_error: + + aws_http_proxy_negotiator_release(&sequence_negotiator->negotiator_base); + + return NULL; +} + +static struct aws_http_proxy_strategy_vtable s_tunneling_sequence_strategy_vtable = { + .create_negotiator = s_create_tunneling_sequence_negotiator, +}; + +static void s_destroy_tunneling_sequence_strategy(struct aws_http_proxy_strategy *proxy_strategy) { + struct aws_http_proxy_strategy_tunneling_sequence *sequence_strategy = proxy_strategy->impl; + + size_t strategy_count = aws_array_list_length(&sequence_strategy->strategies); + for (size_t i = 0; i < strategy_count; ++i) { + struct aws_http_proxy_strategy *strategy = NULL; + if (aws_array_list_get_at(&sequence_strategy->strategies, &strategy, i)) { + continue; + } + + aws_http_proxy_strategy_release(strategy); + } + + aws_array_list_clean_up(&sequence_strategy->strategies); + + aws_mem_release(sequence_strategy->allocator, sequence_strategy); +} + +struct aws_http_proxy_strategy *aws_http_proxy_strategy_new_tunneling_sequence( + struct aws_allocator *allocator, + struct aws_http_proxy_strategy_tunneling_sequence_options *config) { + + if (allocator == NULL || config == NULL) { + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_http_proxy_strategy_tunneling_sequence *sequence_strategy = + aws_mem_calloc(allocator, 1, sizeof(struct aws_http_proxy_strategy_tunneling_sequence)); + if (sequence_strategy == NULL) { + return NULL; + } + + sequence_strategy->strategy_base.impl = sequence_strategy; + sequence_strategy->strategy_base.vtable = &s_tunneling_sequence_strategy_vtable; + sequence_strategy->strategy_base.proxy_connection_type = AWS_HPCT_HTTP_TUNNEL; + sequence_strategy->allocator = allocator; + + aws_ref_count_init( + &sequence_strategy->strategy_base.ref_count, + &sequence_strategy->strategy_base, + (aws_simple_completion_callback *)s_destroy_tunneling_sequence_strategy); + + if (aws_array_list_init_dynamic( + &sequence_strategy->strategies, + allocator, + config->strategy_count, + sizeof(struct aws_http_proxy_strategy *))) { + goto on_error; + } + + for (size_t i = 0; i < config->strategy_count; ++i) { + struct aws_http_proxy_strategy *strategy = config->strategies[i]; + + if (aws_array_list_push_back(&sequence_strategy->strategies, &strategy)) { + goto on_error; + } + + aws_http_proxy_strategy_acquire(strategy); + } + + return &sequence_strategy->strategy_base; + +on_error: + + aws_http_proxy_strategy_release(&sequence_strategy->strategy_base); + + return NULL; +} + +#if defined(_MSC_VER) +# pragma warning(pop) +#endif /* _MSC_VER */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 705baacc8..2636f7cc2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -489,16 +489,26 @@ add_test_case(h1_server_error_from_outgoing_body_callback_stops_sending) add_test_case(h1_server_close_from_off_thread_makes_not_open) add_test_case(h1_server_close_from_on_thread_makes_not_open) -add_test_case(test_http_proxy_connection_proxy_target) -add_test_case(test_http_proxy_connection_channel_failure) -add_test_case(test_http_proxy_connection_connect_failure) -add_test_case(test_https_proxy_connection_success) -add_test_case(test_https_proxy_connection_failure_connect) -add_test_case(test_https_proxy_connection_failure_tls) -add_test_case(test_http_proxy_request_transform) -add_test_case(test_http_proxy_request_transform_basic_auth) -add_test_case(test_http_proxy_uri_rewrite) -add_test_case(test_http_proxy_uri_rewrite_options_star) +add_test_case(test_http_forwarding_proxy_connection_proxy_target) +add_test_case(test_http_forwarding_proxy_connection_channel_failure) +add_test_case(test_http_forwarding_proxy_connection_connect_failure) +add_test_case(test_http_forwarding_proxy_request_transform) +add_test_case(test_http_forwarding_proxy_request_transform_basic_auth) +add_test_case(test_http_forwarding_proxy_request_transform_legacy_basic_auth) +add_test_case(test_http_proxy_request_transform_kerberos) +add_test_case(test_http_proxy_kerberos_token_failure) +add_test_case(test_http_proxy_kerberos_connect_failure) +add_test_case(test_http_proxy_adaptive_identity_success) +add_test_case(test_http_proxy_adaptive_kerberos_success) +add_test_case(test_http_proxy_adaptive_ntlm_success) +add_test_case(test_http_proxy_adaptive_failure) +add_test_case(test_http_forwarding_proxy_uri_rewrite) +add_test_case(test_http_forwarding_proxy_uri_rewrite_options_star) +add_test_case(test_http_tunnel_proxy_connection_success) +add_test_case(test_https_tunnel_proxy_connection_success) +add_test_case(test_http_tunnel_proxy_connection_failure_connect) +add_test_case(test_https_tunnel_proxy_connection_failure_connect) +add_test_case(test_https_tunnel_proxy_connection_failure_tls) # integration tests that require a locally-installed proxy server if (ENABLE_PROXY_INTEGRATION_TESTS) @@ -507,6 +517,8 @@ if (ENABLE_PROXY_INTEGRATION_TESTS) add_test_case(test_http_proxy_connection_get) add_test_case(test_https_proxy_connection_new_destroy) add_test_case(test_https_proxy_connection_get) + add_test_case(test_nested_https_proxy_connection_get) + add_test_case(test_proxy_sequential_negotiation) endif() add_test_case(test_http_connection_monitor_options_is_valid) diff --git a/tests/integration_test_proxy.c b/tests/integration_test_proxy.c index 6585a694d..7b1f8be92 100644 --- a/tests/integration_test_proxy.c +++ b/tests/integration_test_proxy.c @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0. */ +#include #include #include #include @@ -13,6 +14,9 @@ #include "proxy_test_helper.h" +#define HTTP_PROXY_PORT 3128 +#define HTTPS_PROXY_PORT 3129 + static struct proxy_tester tester; static int s_response_status_code = 0; @@ -76,20 +80,27 @@ static void s_aws_http_on_stream_complete_proxy_test(struct aws_http_stream *str aws_condition_variable_notify_one(&context->wait_cvar); } +AWS_STATIC_STRING_FROM_LITERAL(s_proxy_host_name, "localhost"); + static int s_setup_proxy_test( struct aws_allocator *allocator, struct aws_byte_cursor host, - enum proxy_tester_test_mode test_mode) { + enum proxy_tester_test_mode test_mode, + uint16_t port, + struct aws_tls_connection_options *tls_connection_options) { + struct aws_http_proxy_options proxy_options = { - .host = aws_byte_cursor_from_c_str("127.0.0.1"), - .port = 3128, + .connection_type = test_mode == PTTM_HTTP_FORWARD ? AWS_HPCT_HTTP_FORWARD : AWS_HPCT_HTTP_TUNNEL, + .host = aws_byte_cursor_from_string(s_proxy_host_name), + .port = port, + .tls_options = tls_connection_options, }; struct proxy_tester_options options = { .alloc = allocator, .proxy_options = &proxy_options, .host = host, - .port = test_mode == PTTM_HTTP ? 80 : 443, + .port = test_mode == PTTM_HTTPS_TUNNEL ? 443 : 80, .test_mode = test_mode, .failure_type = PTFT_NONE, }; @@ -105,8 +116,10 @@ static int s_do_proxy_request_test( struct aws_byte_cursor host, enum proxy_tester_test_mode test_mode, struct aws_byte_cursor method, - struct aws_byte_cursor path) { - ASSERT_SUCCESS(s_setup_proxy_test(allocator, host, test_mode)); + struct aws_byte_cursor path, + uint16_t port, + struct aws_tls_connection_options *tls_connection_options) { + ASSERT_SUCCESS(s_setup_proxy_test(allocator, host, test_mode, port, tls_connection_options)); ASSERT_TRUE(tester.wait_result == AWS_ERROR_SUCCESS); struct aws_http_message *request = aws_http_message_new_request(allocator); @@ -155,7 +168,8 @@ static int s_do_proxy_request_test( static int s_test_http_proxy_connection_new_destroy(struct aws_allocator *allocator, void *ctx) { (void)ctx; - ASSERT_SUCCESS(s_setup_proxy_test(allocator, aws_byte_cursor_from_c_str("example.org"), PTTM_HTTP)); + ASSERT_SUCCESS(s_setup_proxy_test( + allocator, aws_byte_cursor_from_c_str("example.org"), PTTM_HTTP_FORWARD, HTTP_PROXY_PORT, NULL)); ASSERT_TRUE(tester.wait_result == AWS_ERROR_SUCCESS); ASSERT_SUCCESS(proxy_tester_clean_up(&tester)); @@ -170,16 +184,19 @@ static int s_test_http_proxy_connection_get(struct aws_allocator *allocator, voi return s_do_proxy_request_test( allocator, aws_byte_cursor_from_c_str("example.org"), - PTTM_HTTP, + PTTM_HTTP_FORWARD, aws_byte_cursor_from_c_str("GET"), - aws_byte_cursor_from_c_str("/")); + aws_byte_cursor_from_c_str("/"), + HTTP_PROXY_PORT, + NULL); } AWS_TEST_CASE(test_http_proxy_connection_get, s_test_http_proxy_connection_get); static int s_test_https_proxy_connection_new_destroy(struct aws_allocator *allocator, void *ctx) { (void)ctx; - ASSERT_SUCCESS(s_setup_proxy_test(allocator, aws_byte_cursor_from_c_str("aws.amazon.com"), PTTM_HTTPS)); + ASSERT_SUCCESS(s_setup_proxy_test( + allocator, aws_byte_cursor_from_c_str("aws.amazon.com"), PTTM_HTTPS_TUNNEL, HTTP_PROXY_PORT, NULL)); ASSERT_TRUE(tester.wait_result == AWS_ERROR_SUCCESS); ASSERT_SUCCESS(proxy_tester_clean_up(&tester)); @@ -194,9 +211,11 @@ static int s_test_https_proxy_connection_get(struct aws_allocator *allocator, vo return s_do_proxy_request_test( allocator, aws_byte_cursor_from_c_str("aws.amazon.com"), - PTTM_HTTPS, + PTTM_HTTPS_TUNNEL, aws_byte_cursor_from_c_str("GET"), - aws_byte_cursor_from_c_str("/")); + aws_byte_cursor_from_c_str("/"), + HTTP_PROXY_PORT, + NULL); } AWS_TEST_CASE(test_https_proxy_connection_get, s_test_https_proxy_connection_get); @@ -206,8 +225,143 @@ static int s_test_http_proxy_connection_options_star(struct aws_allocator *alloc return s_do_proxy_request_test( allocator, aws_byte_cursor_from_c_str("example.org"), - PTTM_HTTP, + PTTM_HTTP_FORWARD, aws_byte_cursor_from_c_str("OPTIONS"), - aws_byte_cursor_from_c_str("*")); + aws_byte_cursor_from_c_str("*"), + HTTP_PROXY_PORT, + NULL); } AWS_TEST_CASE(test_http_proxy_connection_options_star, s_test_http_proxy_connection_options_star); + +static int s_test_nested_https_proxy_connection_get(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_http_library_init(allocator); + + struct aws_tls_ctx_options tls_ctx_options; + AWS_ZERO_STRUCT(tls_ctx_options); + + aws_tls_ctx_options_init_default_client(&tls_ctx_options, allocator); + aws_tls_ctx_options_set_alpn_list(&tls_ctx_options, "http/1.1"); + tls_ctx_options.verify_peer = false; + + struct aws_tls_ctx *tls_ctx = aws_tls_client_ctx_new(allocator, &tls_ctx_options); + + struct aws_tls_connection_options tls_connection_options; + AWS_ZERO_STRUCT(tls_connection_options); + + aws_tls_connection_options_init_from_ctx(&tls_connection_options, tls_ctx); + + struct aws_byte_cursor host_name_cursor = aws_byte_cursor_from_string(s_proxy_host_name); + aws_tls_connection_options_set_server_name(&tls_connection_options, allocator, &host_name_cursor); + + ASSERT_SUCCESS(s_do_proxy_request_test( + allocator, + aws_byte_cursor_from_c_str("aws.amazon.com"), + PTTM_HTTPS_TUNNEL, + aws_byte_cursor_from_c_str("GET"), + aws_byte_cursor_from_c_str("/"), + HTTPS_PROXY_PORT, + &tls_connection_options)); + + aws_tls_connection_options_clean_up(&tls_connection_options); + aws_tls_ctx_release(tls_ctx); + aws_tls_ctx_options_clean_up(&tls_ctx_options); + + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE(test_nested_https_proxy_connection_get, s_test_nested_https_proxy_connection_get); + +#include + +AWS_STATIC_STRING_FROM_LITERAL(s_mock_kerberos_token_value, "abcdefABCDEF123"); + +static struct aws_string *s_mock_aws_http_proxy_negotiation_kerberos_get_token_sync_fn( + void *user_data, + int *out_error_code) { + + struct aws_allocator *allocator = user_data; + + *out_error_code = AWS_ERROR_SUCCESS; + return aws_string_new_from_string(allocator, s_mock_kerberos_token_value); +} + +AWS_STATIC_STRING_FROM_LITERAL(s_mock_ntlm_challenge_token_value, "NTLM_RESPONSE"); + +static struct aws_string *s_mock_aws_http_proxy_negotiation_ntlm_get_challenge_token_sync_fn( + void *user_data, + const struct aws_byte_cursor *challenge_value, + int *out_error_code) { + + (void)challenge_value; + + struct aws_allocator *allocator = user_data; + + *out_error_code = AWS_ERROR_SUCCESS; + return aws_string_new_from_string(allocator, s_mock_ntlm_challenge_token_value); +} + +AWS_STATIC_STRING_FROM_LITERAL(s_mock_ntlm_token_value, "NTLM_TOKEN"); + +static struct aws_string *s_mock_aws_http_proxy_negotiation_ntlm_get_token_sync_fn( + void *user_data, + int *out_error_code) { + + struct aws_allocator *allocator = user_data; + + *out_error_code = AWS_ERROR_SUCCESS; + return aws_string_new_from_string(allocator, s_mock_ntlm_token_value); +} + +static int s_test_proxy_sequential_negotiation(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct aws_http_proxy_strategy_tunneling_kerberos_options kerberos_config = { + .get_token = s_mock_aws_http_proxy_negotiation_kerberos_get_token_sync_fn, + .get_token_user_data = allocator, + }; + + struct aws_http_proxy_strategy_tunneling_ntlm_options ntlm_config = { + .get_challenge_token = s_mock_aws_http_proxy_negotiation_ntlm_get_challenge_token_sync_fn, + .get_challenge_token_user_data = allocator, + .get_token = s_mock_aws_http_proxy_negotiation_ntlm_get_token_sync_fn, + }; + + struct aws_http_proxy_strategy_tunneling_adaptive_options adaptive_config = { + .kerberos_options = &kerberos_config, + .ntlm_options = &ntlm_config, + }; + + struct aws_http_proxy_strategy *proxy_strategy = + aws_http_proxy_strategy_new_tunneling_adaptive(allocator, &adaptive_config); + + struct aws_http_proxy_options proxy_options = { + .connection_type = AWS_HPCT_HTTP_TUNNEL, + .host = aws_byte_cursor_from_string(s_proxy_host_name), + .port = HTTPS_PROXY_PORT, + .tls_options = NULL, + .proxy_strategy = proxy_strategy, + }; + + struct proxy_tester_options options = { + .alloc = allocator, + .proxy_options = &proxy_options, + .host = aws_byte_cursor_from_c_str("www.amazon.com"), + .port = 443, + .test_mode = PTTM_HTTP_TUNNEL, + .failure_type = PTFT_NONE, + }; + + ASSERT_SUCCESS(proxy_tester_init(&tester, &options)); + ASSERT_SUCCESS(proxy_tester_wait(&tester, proxy_tester_connection_setup_pred)); + + ASSERT_TRUE(tester.wait_result != AWS_ERROR_SUCCESS); + + ASSERT_SUCCESS(proxy_tester_clean_up(&tester)); + + aws_http_proxy_strategy_release(proxy_strategy); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(test_proxy_sequential_negotiation, s_test_proxy_sequential_negotiation); diff --git a/tests/proxy_test_helper.c b/tests/proxy_test_helper.c index 33ccb7f56..e299234c9 100644 --- a/tests/proxy_test_helper.c +++ b/tests/proxy_test_helper.c @@ -6,11 +6,13 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -28,11 +30,27 @@ enum { TESTER_TIMEOUT_SEC = 60, /* Give enough time for non-sudo users to enter password */ }; +struct testing_channel_bootstrap_wrapper { + struct testing_channel *channel; + struct aws_http_client_bootstrap *bootstrap; +}; + +static struct testing_channel_bootstrap_wrapper *s_get_current_channel_bootstrap_wrapper(struct proxy_tester *tester) { + struct testing_channel_bootstrap_wrapper *wrapper = NULL; + + size_t count = aws_array_list_length(&tester->testing_channels); + aws_array_list_get_at_ptr(&tester->testing_channels, (void **)&wrapper, count - 1); + + return wrapper; +} + void proxy_tester_on_client_connection_setup(struct aws_http_connection *connection, int error_code, void *user_data) { struct proxy_tester *tester = user_data; AWS_FATAL_ASSERT(aws_mutex_lock(&tester->wait_lock) == AWS_OP_SUCCESS); + tester->client_connection_is_setup = true; + if (error_code) { tester->client_connection = NULL; tester->wait_result = error_code; @@ -75,6 +93,11 @@ bool proxy_tester_connection_setup_pred(void *user_data) { return tester->wait_result || tester->client_connection; } +bool proxy_tester_connection_complete_pred(void *user_data) { + struct proxy_tester *tester = user_data; + return tester->client_connection_is_setup; +} + bool proxy_tester_connection_shutdown_pred(void *user_data) { struct proxy_tester *tester = user_data; return tester->wait_result || tester->client_connection_is_shutdown; @@ -92,25 +115,36 @@ int proxy_tester_init(struct proxy_tester *tester, const struct proxy_tester_opt aws_http_library_init(options->alloc); + ASSERT_SUCCESS(aws_array_list_init_dynamic( + &tester->testing_channels, options->alloc, 1, sizeof(struct testing_channel_bootstrap_wrapper))); + tester->host = options->host; tester->port = options->port; - tester->proxy_options = options->proxy_options; + tester->proxy_options = *options->proxy_options; tester->test_mode = options->test_mode; tester->failure_type = options->failure_type; ASSERT_SUCCESS(aws_byte_buf_init(&tester->connection_host_name, tester->alloc, 128)); - struct aws_logger_standard_options logger_options = { - .level = AWS_LOG_LEVEL_TRACE, - .file = stderr, - }; - - ASSERT_SUCCESS(aws_logger_init_standard(&tester->logger, tester->alloc, &logger_options)); - aws_logger_set(&tester->logger); - ASSERT_SUCCESS(aws_mutex_init(&tester->wait_lock)); ASSERT_SUCCESS(aws_condition_variable_init(&tester->wait_cvar)); + ASSERT_SUCCESS( + aws_array_list_init_dynamic(&tester->connect_requests, tester->alloc, 1, sizeof(struct aws_http_message *))); + + uint32_t connect_response_count = 1; + if (options->desired_connect_response_count > connect_response_count) { + connect_response_count = options->desired_connect_response_count; + } + ASSERT_SUCCESS(aws_array_list_init_dynamic( + &tester->desired_connect_responses, tester->alloc, connect_response_count, sizeof(struct aws_string *))); + + for (size_t i = 0; i < options->desired_connect_response_count; ++i) { + struct aws_byte_cursor response_cursor = options->desired_connect_responses[i]; + struct aws_string *response = aws_string_new_from_cursor(tester->alloc, &response_cursor); + ASSERT_SUCCESS(aws_array_list_push_back(&tester->desired_connect_responses, &response)); + } + tester->event_loop_group = aws_event_loop_group_new_default(tester->alloc, 1, NULL); struct aws_host_resolver_default_options resolver_options = { @@ -134,7 +168,7 @@ int proxy_tester_init(struct proxy_tester *tester, const struct proxy_tester_opt tester->client_bootstrap = aws_client_bootstrap_new(tester->alloc, &bootstrap_options); ASSERT_NOT_NULL(tester->client_bootstrap); - bool use_tls = options->test_mode == PTTM_HTTPS; + bool use_tls = options->test_mode == PTTM_HTTPS_TUNNEL; if (use_tls) { aws_tls_ctx_options_init_default_client(&tester->tls_ctx_options, tester->alloc); aws_tls_ctx_options_set_alpn_list(&tester->tls_ctx_options, "http/1.1"); @@ -171,29 +205,42 @@ int proxy_tester_init(struct proxy_tester *tester, const struct proxy_tester_opt int proxy_tester_clean_up(struct proxy_tester *tester) { if (tester->client_connection) { - if (tester->client_connection) { - aws_http_connection_release(tester->client_connection); - } + aws_http_connection_release(tester->client_connection); } - if (tester->testing_channel) { - ASSERT_SUCCESS(testing_channel_clean_up(tester->testing_channel)); - while (!testing_channel_is_shutdown_completed(tester->testing_channel)) { - aws_thread_current_sleep(1000000000); + size_t channel_count = aws_array_list_length(&tester->testing_channels); + for (size_t i = 0; i < channel_count; ++i) { + struct testing_channel_bootstrap_wrapper wrapper; + aws_array_list_get_at(&tester->testing_channels, &wrapper, i); + struct testing_channel *channel = wrapper.channel; + if (channel) { + ASSERT_SUCCESS(testing_channel_clean_up(channel)); + while (!testing_channel_is_shutdown_completed(channel)) { + aws_thread_current_sleep(1000000000); + } + + aws_mem_release(tester->alloc, channel); } - - aws_mem_release(tester->alloc, tester->testing_channel); } ASSERT_SUCCESS(proxy_tester_wait(tester, proxy_tester_connection_shutdown_pred)); - if (tester->http_bootstrap != NULL) { - if (tester->testing_channel == NULL && tester->http_bootstrap->user_data) { - aws_http_proxy_user_data_destroy(tester->http_bootstrap->user_data); + for (size_t i = 0; i < channel_count; ++i) { + struct testing_channel_bootstrap_wrapper wrapper; + aws_array_list_get_at(&tester->testing_channels, &wrapper, i); + if (wrapper.bootstrap != NULL) { + if (channel_count == 0 && wrapper.bootstrap->user_data) { + aws_http_proxy_user_data_destroy(wrapper.bootstrap->user_data); + } + if (i + 1 < channel_count) { + wrapper.bootstrap->on_shutdown(tester->client_connection, 0, wrapper.bootstrap->user_data); + } + aws_mem_release(tester->alloc, wrapper.bootstrap); } - aws_mem_release(tester->alloc, tester->http_bootstrap); } + aws_array_list_clean_up(&tester->testing_channels); + aws_client_bootstrap_release(tester->client_bootstrap); aws_host_resolver_release(tester->host_resolver); @@ -205,8 +252,25 @@ int proxy_tester_clean_up(struct proxy_tester *tester) { aws_tls_ctx_options_clean_up(&tester->tls_ctx_options); } + size_t connect_request_count = aws_array_list_length(&tester->connect_requests); + for (size_t i = 0; i < connect_request_count; ++i) { + struct aws_http_message *request = NULL; + + aws_array_list_get_at(&tester->connect_requests, &request, i); + aws_http_message_release(request); + } + aws_array_list_clean_up(&tester->connect_requests); + + size_t connect_response_count = aws_array_list_length(&tester->desired_connect_responses); + for (size_t i = 0; i < connect_response_count; ++i) { + struct aws_string *response = NULL; + + aws_array_list_get_at(&tester->desired_connect_responses, &response, i); + aws_string_destroy(response); + } + aws_array_list_clean_up(&tester->desired_connect_responses); + aws_http_library_clean_up(); - aws_logger_clean_up(&tester->logger); aws_byte_buf_clean_up(&tester->connection_host_name); @@ -220,17 +284,26 @@ static void s_testing_channel_shutdown_callback(int error_code, void *user_data) tester->wait_result = error_code; } - tester->http_bootstrap->on_shutdown( - tester->client_connection, tester->wait_result, tester->http_bootstrap->user_data); + struct testing_channel_bootstrap_wrapper *wrapper = s_get_current_channel_bootstrap_wrapper(tester); + + wrapper->bootstrap->on_shutdown(tester->client_connection, tester->wait_result, wrapper->bootstrap->user_data); } -int proxy_tester_create_testing_channel_connection(struct proxy_tester *tester) { - tester->testing_channel = aws_mem_calloc(tester->alloc, 1, sizeof(struct testing_channel)); +int proxy_tester_create_testing_channel_connection( + struct proxy_tester *tester, + struct aws_http_client_bootstrap *http_bootstrap) { + + struct testing_channel_bootstrap_wrapper *old_wrapper = s_get_current_channel_bootstrap_wrapper(tester); + if (old_wrapper != NULL) { + old_wrapper->channel->channel_shutdown = NULL; + } + + struct testing_channel *testing_channel = aws_mem_calloc(tester->alloc, 1, sizeof(struct testing_channel)); struct aws_testing_channel_options test_channel_options = {.clock_fn = aws_high_res_clock_get_ticks}; - ASSERT_SUCCESS(testing_channel_init(tester->testing_channel, tester->alloc, &test_channel_options)); - tester->testing_channel->channel_shutdown = s_testing_channel_shutdown_callback; - tester->testing_channel->channel_shutdown_user_data = tester; + ASSERT_SUCCESS(testing_channel_init(testing_channel, tester->alloc, &test_channel_options)); + testing_channel->channel_shutdown = s_testing_channel_shutdown_callback; + testing_channel->channel_shutdown_user_data = tester; /* Use small window so that we can observe it opening in tests. * Channel may wait until the window is small before issuing the increment command. */ @@ -239,26 +312,107 @@ int proxy_tester_create_testing_channel_connection(struct proxy_tester *tester) aws_http_connection_new_http1_1_client(tester->alloc, true, 256, &http1_options); ASSERT_NOT_NULL(connection); - connection->user_data = tester->http_bootstrap->user_data; + connection->user_data = http_bootstrap->user_data; connection->client_data = &connection->client_or_server_data.client; - connection->proxy_request_transform = tester->http_bootstrap->proxy_request_transform; + connection->proxy_request_transform = http_bootstrap->proxy_request_transform; - struct aws_channel_slot *slot = aws_channel_slot_new(tester->testing_channel->channel); + struct aws_channel_slot *slot = aws_channel_slot_new(testing_channel->channel); ASSERT_NOT_NULL(slot); - ASSERT_SUCCESS(aws_channel_slot_insert_end(tester->testing_channel->channel, slot)); + ASSERT_SUCCESS(aws_channel_slot_insert_end(testing_channel->channel, slot)); ASSERT_SUCCESS(aws_channel_slot_set_handler(slot, &connection->channel_handler)); connection->vtable->on_channel_handler_installed(&connection->channel_handler, slot); - testing_channel_drain_queued_tasks(tester->testing_channel); + testing_channel_drain_queued_tasks(testing_channel); tester->client_connection = connection; + struct testing_channel_bootstrap_wrapper wrapper; + wrapper.channel = testing_channel; + wrapper.bootstrap = http_bootstrap; + aws_array_list_push_back(&tester->testing_channels, &wrapper); + + return AWS_OP_SUCCESS; +} + +bool s_line_feed_predicate(uint8_t value) { + return value == '\r'; +} + +/* + * A very crude, sloppy http request parser that does just enough to test what we want to test + */ +static int s_record_connect_request(struct aws_byte_buf *request_buffer, struct proxy_tester *tester) { + struct aws_byte_cursor request_cursor = aws_byte_cursor_from_buf(request_buffer); + + struct aws_array_list lines; + ASSERT_SUCCESS(aws_array_list_init_dynamic(&lines, tester->alloc, 10, sizeof(struct aws_byte_cursor))); + aws_byte_cursor_split_on_char(&request_cursor, '\n', &lines); + + size_t line_count = aws_array_list_length(&lines); + ASSERT_TRUE(line_count > 1); + + struct aws_http_message *message = aws_http_message_new_request(tester->alloc); + + struct aws_byte_cursor first_line_cursor; + AWS_ZERO_STRUCT(first_line_cursor); + aws_array_list_get_at(&lines, &first_line_cursor, 0); + first_line_cursor = aws_byte_cursor_trim_pred(&first_line_cursor, s_line_feed_predicate); + + struct aws_byte_cursor method_cursor; + AWS_ZERO_STRUCT(method_cursor); + aws_byte_cursor_next_split(&first_line_cursor, ' ', &method_cursor); + + aws_http_message_set_request_method(message, method_cursor); + + aws_byte_cursor_advance(&first_line_cursor, method_cursor.len + 1); + + struct aws_byte_cursor uri_cursor; + AWS_ZERO_STRUCT(uri_cursor); + aws_byte_cursor_next_split(&first_line_cursor, ' ', &uri_cursor); + + aws_http_message_set_request_path(message, uri_cursor); + + for (size_t i = 1; i < line_count; ++i) { + struct aws_byte_cursor line_cursor; + AWS_ZERO_STRUCT(line_cursor); + aws_array_list_get_at(&lines, &line_cursor, i); + line_cursor = aws_byte_cursor_trim_pred(&line_cursor, s_line_feed_predicate); + + if (line_cursor.len == 0) { + break; + } + + struct aws_byte_cursor name_cursor; + AWS_ZERO_STRUCT(name_cursor); + aws_byte_cursor_next_split(&line_cursor, ':', &name_cursor); + + aws_byte_cursor_advance(&line_cursor, name_cursor.len + 1); + line_cursor = aws_byte_cursor_trim_pred(&line_cursor, aws_isspace); + + struct aws_http_header header = { + .name = name_cursor, + .value = line_cursor, + }; + + aws_http_message_add_header(message, header); + } + + /* we don't care about the body */ + + aws_array_list_push_back(&tester->connect_requests, &message); + + aws_array_list_clean_up(&lines); + return AWS_OP_SUCCESS; } int proxy_tester_verify_connect_request(struct proxy_tester *tester) { struct aws_byte_buf output; ASSERT_SUCCESS(aws_byte_buf_init(&output, tester->alloc, 1024)); - ASSERT_SUCCESS(testing_channel_drain_written_messages(tester->testing_channel, &output)); + + struct testing_channel *testing_channel = proxy_tester_get_current_channel(tester); + ASSERT_NOT_NULL(testing_channel); + + ASSERT_SUCCESS(testing_channel_drain_written_messages(testing_channel, &output)); char connect_request_buffer[1024]; snprintf( @@ -279,6 +433,8 @@ int proxy_tester_verify_connect_request(struct proxy_tester *tester) { ASSERT_TRUE(aws_byte_cursor_eq(&first_line_cursor, &expected_connect_message_first_line_cursor)); + ASSERT_SUCCESS(s_record_connect_request(&output, tester)); + aws_byte_buf_clean_up(&output); return AWS_OP_SUCCESS; @@ -288,18 +444,27 @@ int proxy_tester_send_connect_response(struct proxy_tester *tester) { (void)tester; const char *response_string = NULL; - if (tester->failure_type == PTFT_CONNECT_REQUEST) { - response_string = "HTTP/1.0 401 Unauthorized\r\n\r\n"; + + size_t desired_response_count = aws_array_list_length(&tester->desired_connect_responses); + if (desired_response_count > 0) { + struct aws_string *response = NULL; + aws_array_list_get_at(&tester->desired_connect_responses, &response, tester->current_response_index++); + response_string = (const char *)response->bytes; + + } else if (tester->failure_type == PTFT_CONNECT_REQUEST) { + response_string = "HTTP/1.0 407 Unauthorized\r\n\r\n"; } else { /* adding close here because it's an edge case we need to exercise. The desired behavior is that it has * absolutely no effect. */ response_string = "HTTP/1.0 200 Connection established\r\nconnection: close\r\n\r\n"; } + struct testing_channel *channel = proxy_tester_get_current_channel(tester); + /* send response */ - ASSERT_SUCCESS(testing_channel_push_read_str(tester->testing_channel, response_string)); + ASSERT_SUCCESS(testing_channel_push_read_str(channel, response_string)); - testing_channel_drain_queued_tasks(tester->testing_channel); + testing_channel_drain_queued_tasks(channel); return AWS_OP_SUCCESS; } @@ -321,3 +486,12 @@ int proxy_tester_verify_connection_attempt_was_to_proxy( return AWS_OP_SUCCESS; } + +struct testing_channel *proxy_tester_get_current_channel(struct proxy_tester *tester) { + struct testing_channel_bootstrap_wrapper *wrapper = s_get_current_channel_bootstrap_wrapper(tester); + if (wrapper == NULL) { + return NULL; + } + + return wrapper->channel; +} diff --git a/tests/proxy_test_helper.h b/tests/proxy_test_helper.h index 6daaaf95f..64f507df5 100644 --- a/tests/proxy_test_helper.h +++ b/tests/proxy_test_helper.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -19,8 +20,9 @@ struct testing_channel; typedef void(aws_http_release_connection_fn)(struct aws_http_connection *connection); enum proxy_tester_test_mode { - PTTM_HTTP = 0, - PTTM_HTTPS, + PTTM_HTTP_FORWARD = 0, + PTTM_HTTP_TUNNEL, + PTTM_HTTPS_TUNNEL, }; enum proxy_tester_failure_type { @@ -29,6 +31,7 @@ enum proxy_tester_failure_type { PTFT_TLS_NEGOTIATION, PTFT_CHANNEL, PTFT_CONNECTION, + PTFT_PROXY_STRATEGY, }; struct proxy_tester_options { @@ -38,6 +41,9 @@ struct proxy_tester_options { uint16_t port; enum proxy_tester_test_mode test_mode; enum proxy_tester_failure_type failure_type; + + uint32_t desired_connect_response_count; + struct aws_byte_cursor *desired_connect_responses; }; struct proxy_tester { @@ -51,7 +57,7 @@ struct proxy_tester { struct aws_tls_ctx_options tls_ctx_options; struct aws_tls_connection_options tls_connection_options; - struct aws_http_proxy_options *proxy_options; + struct aws_http_proxy_options proxy_options; struct aws_byte_cursor host; uint16_t port; @@ -59,9 +65,9 @@ struct proxy_tester { enum proxy_tester_failure_type failure_type; struct aws_http_connection *client_connection; - struct aws_http_client_bootstrap *http_bootstrap; - struct testing_channel *testing_channel; + struct aws_array_list testing_channels; + bool client_connection_is_setup; bool client_connection_is_shutdown; /* If we need to wait for some async process*/ @@ -76,11 +82,17 @@ struct proxy_tester { struct aws_byte_buf connection_host_name; uint16_t connection_port; + + struct aws_array_list connect_requests; + + uint32_t current_response_index; + struct aws_array_list desired_connect_responses; }; int proxy_tester_wait(struct proxy_tester *tester, bool (*pred)(void *user_data)); bool proxy_tester_connection_setup_pred(void *user_data); +bool proxy_tester_connection_complete_pred(void *user_data); bool proxy_tester_connection_shutdown_pred(void *user_data); bool proxy_tester_request_complete_pred_fn(void *user_data); @@ -97,7 +109,9 @@ void proxy_tester_on_client_connection_shutdown( void proxy_tester_on_client_bootstrap_shutdown(void *user_data); -int proxy_tester_create_testing_channel_connection(struct proxy_tester *tester); +int proxy_tester_create_testing_channel_connection( + struct proxy_tester *tester, + struct aws_http_client_bootstrap *http_bootstrap); int proxy_tester_verify_connect_request(struct proxy_tester *tester); @@ -108,4 +122,6 @@ int proxy_tester_verify_connection_attempt_was_to_proxy( struct aws_byte_cursor expected_host, uint16_t expected_port); +struct testing_channel *proxy_tester_get_current_channel(struct proxy_tester *tester); + #endif /* AWS_HTTP_PROXY_TEST_HELPER_H */ diff --git a/tests/test_connection_manager.c b/tests/test_connection_manager.c index 367e835c1..076e01366 100644 --- a/tests/test_connection_manager.c +++ b/tests/test_connection_manager.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -25,7 +26,11 @@ # pragma warning(disable : 4232) /* function pointer to dll symbol */ #endif -enum new_connection_result_type { AWS_NCRT_SUCCESS, AWS_NCRT_ERROR_VIA_CALLBACK, AWS_NCRT_ERROR_FROM_CREATE }; +enum new_connection_result_type { + AWS_NCRT_SUCCESS, + AWS_NCRT_ERROR_VIA_CALLBACK, + AWS_NCRT_ERROR_FROM_CREATE, +}; struct mock_connection { enum new_connection_result_type result; @@ -393,7 +398,10 @@ static int s_cm_tester_clean_up(void) { static int s_test_connection_manager_setup_shutdown(struct aws_allocator *allocator, void *ctx) { (void)ctx; - struct cm_tester_options options = {.allocator = allocator, .max_connections = 5}; + struct cm_tester_options options = { + .allocator = allocator, + .max_connections = 5, + }; ASSERT_SUCCESS(s_cm_tester_init(&options)); @@ -406,7 +414,10 @@ AWS_TEST_CASE(test_connection_manager_setup_shutdown, s_test_connection_manager_ static int s_test_connection_manager_single_connection(struct aws_allocator *allocator, void *ctx) { (void)ctx; - struct cm_tester_options options = {.allocator = allocator, .max_connections = 5}; + struct cm_tester_options options = { + .allocator = allocator, + .max_connections = 5, + }; ASSERT_SUCCESS(s_cm_tester_init(&options)); @@ -425,7 +436,10 @@ AWS_TEST_CASE(test_connection_manager_single_connection, s_test_connection_manag static int s_test_connection_manager_many_connections(struct aws_allocator *allocator, void *ctx) { (void)ctx; - struct cm_tester_options options = {.allocator = allocator, .max_connections = 20}; + struct cm_tester_options options = { + .allocator = allocator, + .max_connections = 20, + }; ASSERT_SUCCESS(s_cm_tester_init(&options)); @@ -444,7 +458,10 @@ AWS_TEST_CASE(test_connection_manager_many_connections, s_test_connection_manage static int s_test_connection_manager_acquire_release(struct aws_allocator *allocator, void *ctx) { (void)ctx; - struct cm_tester_options options = {.allocator = allocator, .max_connections = 4}; + struct cm_tester_options options = { + .allocator = allocator, + .max_connections = 4, + }; ASSERT_SUCCESS(s_cm_tester_init(&options)); @@ -467,7 +484,10 @@ AWS_TEST_CASE(test_connection_manager_acquire_release, s_test_connection_manager static int s_test_connection_manager_close_and_release(struct aws_allocator *allocator, void *ctx) { (void)ctx; - struct cm_tester_options options = {.allocator = allocator, .max_connections = 4}; + struct cm_tester_options options = { + .allocator = allocator, + .max_connections = 4, + }; ASSERT_SUCCESS(s_cm_tester_init(&options)); diff --git a/tests/test_proxy.c b/tests/test_proxy.c index 5f9c6c0b0..828302ae2 100644 --- a/tests/test_proxy.c +++ b/tests/test_proxy.c @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -28,8 +29,8 @@ static uint16_t s_proxy_port = 777; AWS_STATIC_STRING_FROM_LITERAL(s_mock_request_method, "GET"); AWS_STATIC_STRING_FROM_LITERAL(s_mock_request_path, "/"); AWS_STATIC_STRING_FROM_LITERAL(s_mock_request_host, "aws.amazon.com"); -AWS_STATIC_STRING_FROM_LITERAL(s_expected_auth_header_name, "Proxy-Authorization"); -AWS_STATIC_STRING_FROM_LITERAL(s_expected_auth_header_value, "Basic U29tZVVzZXI6U3VwZXJTZWNyZXQ="); +AWS_STATIC_STRING_FROM_LITERAL(s_expected_basic_auth_header_name, "Proxy-Authorization"); +AWS_STATIC_STRING_FROM_LITERAL(s_expected_basic_auth_header_value, "Basic U29tZVVzZXI6U3VwZXJTZWNyZXQ="); AWS_STATIC_STRING_FROM_LITERAL(s_mock_request_username, "SomeUser"); AWS_STATIC_STRING_FROM_LITERAL(s_mock_request_password, "SuperSecret"); @@ -69,7 +70,21 @@ static struct aws_http_message *s_build_http_request(struct aws_allocator *alloc aws_byte_cursor_from_string(s_mock_request_host)); } -static bool s_is_header_in_request(struct aws_http_message *request, struct aws_http_header *header) { +static bool s_is_header_in_request(struct aws_http_message *request, struct aws_byte_cursor header_name) { + size_t header_count = aws_http_message_get_header_count(request); + for (size_t i = 0; i < header_count; ++i) { + struct aws_http_header current_header; + ASSERT_SUCCESS(aws_http_message_get_header(request, ¤t_header, i)); + + if (aws_byte_cursor_eq_ignore_case(¤t_header.name, &header_name)) { + return true; + } + } + + return false; +} + +static bool s_is_header_and_value_in_request(struct aws_http_message *request, struct aws_http_header *header) { size_t header_count = aws_http_message_get_header_count(request); for (size_t i = 0; i < header_count; ++i) { struct aws_http_header current_header; @@ -127,8 +142,7 @@ static int s_test_aws_proxy_new_socket_channel(struct aws_socket_channel_bootstr if (tester.failure_type == PTFT_CHANNEL) { tester.wait_result = AWS_ERROR_UNKNOWN; } else if (tester.failure_type != PTFT_CONNECTION) { - tester.http_bootstrap = channel_options->user_data; - ASSERT_SUCCESS(proxy_tester_create_testing_channel_connection(&tester)); + ASSERT_SUCCESS(proxy_tester_create_testing_channel_connection(&tester, channel_options->user_data)); } aws_mutex_unlock(&tester.wait_lock); @@ -151,12 +165,20 @@ static int s_test_aws_proxy_new_socket_channel(struct aws_socket_channel_bootstr struct aws_http_client_bootstrap *http_bootstrap = channel_options->user_data; http_bootstrap->on_setup(tester.client_connection, AWS_ERROR_SUCCESS, http_bootstrap->user_data); - testing_channel_run_currently_queued_tasks(tester.testing_channel); + struct testing_channel *channel = proxy_tester_get_current_channel(&tester); + if (tester.failure_type == PTFT_PROXY_STRATEGY) { + testing_channel_drain_queued_tasks(channel); + } else { + testing_channel_run_currently_queued_tasks(channel); + } - if (tester.test_mode == PTTM_HTTPS) { - /* For TLS proxies, send the CONNECT request and response */ - ASSERT_SUCCESS(proxy_tester_verify_connect_request(&tester)); - ASSERT_SUCCESS(proxy_tester_send_connect_response(&tester)); + if (tester.failure_type == PTFT_NONE || tester.failure_type == PTFT_CONNECT_REQUEST || + tester.failure_type == PTFT_TLS_NEGOTIATION) { + if (tester.proxy_options.connection_type == AWS_HPCT_HTTP_TUNNEL) { + /* For tunnel proxies, send the CONNECT request and response */ + ASSERT_SUCCESS(proxy_tester_verify_connect_request(&tester)); + ASSERT_SUCCESS(proxy_tester_send_connect_response(&tester)); + } } return AWS_OP_SUCCESS; @@ -166,38 +188,46 @@ struct aws_http_connection_system_vtable s_proxy_connection_system_vtable = { .new_socket_channel = s_test_aws_proxy_new_socket_channel, }; +struct mocked_proxy_test_options { + enum proxy_tester_test_mode test_mode; + enum proxy_tester_failure_type failure_type; + struct aws_http_proxy_strategy *proxy_strategy; + + enum aws_http_proxy_authentication_type auth_type; + struct aws_byte_cursor legacy_basic_username; + struct aws_byte_cursor legacy_basic_password; + + uint32_t mocked_response_count; + struct aws_byte_cursor *mocked_responses; +}; + /* * Basic setup common to all mocked proxy tests - set vtables, options, call init, wait for setup completion */ -static int s_setup_proxy_test( - struct aws_allocator *allocator, - enum proxy_tester_test_mode test_mode, - enum proxy_tester_failure_type failure_type, - enum aws_http_proxy_authentication_type auth_type) { - - (void)auth_type; +static int s_setup_proxy_test(struct aws_allocator *allocator, struct mocked_proxy_test_options *config) { aws_http_connection_set_system_vtable(&s_proxy_connection_system_vtable); aws_http_proxy_system_set_vtable(&s_proxy_table_for_tls); struct aws_http_proxy_options proxy_options = { + .connection_type = (config->test_mode == PTTM_HTTP_FORWARD) ? AWS_HPCT_HTTP_FORWARD : AWS_HPCT_HTTP_TUNNEL, .host = aws_byte_cursor_from_c_str(s_proxy_host_name), .port = s_proxy_port, + .proxy_strategy = config->proxy_strategy, + .auth_type = config->auth_type, + .auth_username = config->legacy_basic_username, + .auth_password = config->legacy_basic_password, }; - if (auth_type == AWS_HPAT_BASIC) { - proxy_options.auth_type = AWS_HPAT_BASIC; - proxy_options.auth_username = aws_byte_cursor_from_string(s_mock_request_username); - proxy_options.auth_password = aws_byte_cursor_from_string(s_mock_request_password); - } - struct proxy_tester_options options = { .alloc = allocator, .proxy_options = &proxy_options, .host = aws_byte_cursor_from_c_str(s_host_name), .port = s_port, - .test_mode = test_mode, - .failure_type = failure_type, + .test_mode = config->test_mode, + .failure_type = config->failure_type, + .desired_connect_response_count = config->mocked_response_count, + .desired_connect_responses = config->mocked_responses, }; ASSERT_SUCCESS(proxy_tester_init(&tester, &options)); @@ -208,13 +238,18 @@ static int s_setup_proxy_test( } /* - * For plaintext proxy connections: + * For forwarding proxy connections: * If we do pass in proxy options, verify we try and connect to the proxy */ -static int s_test_http_proxy_connection_proxy_target(struct aws_allocator *allocator, void *ctx) { +static int s_test_http_forwarding_proxy_connection_proxy_target(struct aws_allocator *allocator, void *ctx) { (void)ctx; - ASSERT_SUCCESS(s_setup_proxy_test(allocator, PTTM_HTTP, PTFT_NONE, AWS_HPAT_NONE)); + struct mocked_proxy_test_options options = { + .test_mode = PTTM_HTTP_FORWARD, + .failure_type = PTFT_NONE, + }; + + ASSERT_SUCCESS(s_setup_proxy_test(allocator, &options)); ASSERT_SUCCESS(proxy_tester_verify_connection_attempt_was_to_proxy( &tester, aws_byte_cursor_from_c_str(s_proxy_host_name), s_proxy_port)); @@ -223,16 +258,21 @@ static int s_test_http_proxy_connection_proxy_target(struct aws_allocator *alloc return AWS_OP_SUCCESS; } -AWS_TEST_CASE(test_http_proxy_connection_proxy_target, s_test_http_proxy_connection_proxy_target); +AWS_TEST_CASE(test_http_forwarding_proxy_connection_proxy_target, s_test_http_forwarding_proxy_connection_proxy_target); /* - * For plaintext proxy connections: + * For forwarding proxy connections: * Verify a channel creation failure cleans up properly */ -static int s_test_http_proxy_connection_channel_failure(struct aws_allocator *allocator, void *ctx) { +static int s_test_http_forwarding_proxy_connection_channel_failure(struct aws_allocator *allocator, void *ctx) { (void)ctx; - ASSERT_SUCCESS(s_setup_proxy_test(allocator, PTTM_HTTP, PTFT_CHANNEL, AWS_HPAT_NONE)); + struct mocked_proxy_test_options options = { + .test_mode = PTTM_HTTP_FORWARD, + .failure_type = PTFT_CHANNEL, + }; + + ASSERT_SUCCESS(s_setup_proxy_test(allocator, &options)); ASSERT_SUCCESS(proxy_tester_verify_connection_attempt_was_to_proxy( &tester, aws_byte_cursor_from_c_str(s_proxy_host_name), s_proxy_port)); @@ -243,16 +283,23 @@ static int s_test_http_proxy_connection_channel_failure(struct aws_allocator *al return AWS_OP_SUCCESS; } -AWS_TEST_CASE(test_http_proxy_connection_channel_failure, s_test_http_proxy_connection_channel_failure); +AWS_TEST_CASE( + test_http_forwarding_proxy_connection_channel_failure, + s_test_http_forwarding_proxy_connection_channel_failure); /* - * For plaintext proxy connections: + * For forwarding proxy connections: * Verify a connection establishment failure cleans up properly */ -static int s_test_http_proxy_connection_connect_failure(struct aws_allocator *allocator, void *ctx) { +static int s_test_http_forwarding_proxy_connection_connect_failure(struct aws_allocator *allocator, void *ctx) { (void)ctx; - ASSERT_SUCCESS(s_setup_proxy_test(allocator, PTTM_HTTP, PTFT_CONNECTION, AWS_HPAT_NONE)); + struct mocked_proxy_test_options options = { + .test_mode = PTTM_HTTP_FORWARD, + .failure_type = PTFT_CONNECTION, + }; + + ASSERT_SUCCESS(s_setup_proxy_test(allocator, &options)); ASSERT_SUCCESS(proxy_tester_verify_connection_attempt_was_to_proxy( &tester, aws_byte_cursor_from_c_str(s_proxy_host_name), s_proxy_port)); @@ -263,16 +310,48 @@ static int s_test_http_proxy_connection_connect_failure(struct aws_allocator *al return AWS_OP_SUCCESS; } -AWS_TEST_CASE(test_http_proxy_connection_connect_failure, s_test_http_proxy_connection_connect_failure); +AWS_TEST_CASE( + test_http_forwarding_proxy_connection_connect_failure, + s_test_http_forwarding_proxy_connection_connect_failure); /* - * For tls-enabled proxy connections: + * For tls-enabled tunneling proxy connections: * Test the happy path by verifying CONNECT request, tls upgrade attempt */ -static int s_test_https_proxy_connection_success(struct aws_allocator *allocator, void *ctx) { +static int s_test_https_tunnel_proxy_connection_success(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct mocked_proxy_test_options options = { + .test_mode = PTTM_HTTPS_TUNNEL, + .failure_type = PTFT_NONE, + }; + + ASSERT_SUCCESS(s_setup_proxy_test(allocator, &options)); + + ASSERT_SUCCESS(proxy_tester_verify_connection_attempt_was_to_proxy( + &tester, aws_byte_cursor_from_c_str(s_proxy_host_name), s_proxy_port)); + ASSERT_TRUE(tester.client_connection != NULL); + ASSERT_TRUE(tester.wait_result == AWS_ERROR_SUCCESS); + + ASSERT_SUCCESS(proxy_tester_clean_up(&tester)); + + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE(test_https_tunnel_proxy_connection_success, s_test_https_tunnel_proxy_connection_success); + +/* + * For plaintext tunneling proxy connections: + * Test the happy path by verifying CONNECT request + */ +static int s_test_http_tunnel_proxy_connection_success(struct aws_allocator *allocator, void *ctx) { (void)ctx; - ASSERT_SUCCESS(s_setup_proxy_test(allocator, PTTM_HTTPS, PTFT_NONE, AWS_HPAT_NONE)); + struct mocked_proxy_test_options options = { + .test_mode = PTTM_HTTP_TUNNEL, + .failure_type = PTFT_NONE, + }; + + ASSERT_SUCCESS(s_setup_proxy_test(allocator, &options)); ASSERT_SUCCESS(proxy_tester_verify_connection_attempt_was_to_proxy( &tester, aws_byte_cursor_from_c_str(s_proxy_host_name), s_proxy_port)); @@ -283,16 +362,21 @@ static int s_test_https_proxy_connection_success(struct aws_allocator *allocator return AWS_OP_SUCCESS; } -AWS_TEST_CASE(test_https_proxy_connection_success, s_test_https_proxy_connection_success); +AWS_TEST_CASE(test_http_tunnel_proxy_connection_success, s_test_http_tunnel_proxy_connection_success); /* - * For tls-enabled proxy connections: + * For tls-enabled tunneling proxy connections: * If the CONNECT request fails, verify error propagation and cleanup */ -static int s_test_https_proxy_connection_failure_connect(struct aws_allocator *allocator, void *ctx) { +static int s_test_https_tunnel_proxy_connection_failure_connect(struct aws_allocator *allocator, void *ctx) { (void)ctx; - ASSERT_SUCCESS(s_setup_proxy_test(allocator, PTTM_HTTPS, PTFT_CONNECT_REQUEST, AWS_HPAT_NONE)); + struct mocked_proxy_test_options options = { + .test_mode = PTTM_HTTPS_TUNNEL, + .failure_type = PTFT_CONNECT_REQUEST, + }; + + ASSERT_SUCCESS(s_setup_proxy_test(allocator, &options)); ASSERT_SUCCESS(proxy_tester_verify_connection_attempt_was_to_proxy( &tester, aws_byte_cursor_from_c_str(s_proxy_host_name), s_proxy_port)); @@ -303,16 +387,46 @@ static int s_test_https_proxy_connection_failure_connect(struct aws_allocator *a return AWS_OP_SUCCESS; } -AWS_TEST_CASE(test_https_proxy_connection_failure_connect, s_test_https_proxy_connection_failure_connect); +AWS_TEST_CASE(test_https_tunnel_proxy_connection_failure_connect, s_test_https_tunnel_proxy_connection_failure_connect); /* - * For tls-enabled proxy connections: + * For plaintext tunneling proxy connections: + * If the CONNECT request fails, verify error propagation and cleanup + */ +static int s_test_http_tunnel_proxy_connection_failure_connect(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct mocked_proxy_test_options options = { + .test_mode = PTTM_HTTP_TUNNEL, + .failure_type = PTFT_CONNECT_REQUEST, + }; + + ASSERT_SUCCESS(s_setup_proxy_test(allocator, &options)); + + ASSERT_SUCCESS(proxy_tester_verify_connection_attempt_was_to_proxy( + &tester, aws_byte_cursor_from_c_str(s_proxy_host_name), s_proxy_port)); + ASSERT_TRUE(tester.client_connection == NULL); + ASSERT_TRUE(tester.wait_result != AWS_ERROR_SUCCESS); + + ASSERT_SUCCESS(proxy_tester_clean_up(&tester)); + + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE(test_http_tunnel_proxy_connection_failure_connect, s_test_http_tunnel_proxy_connection_failure_connect); + +/* + * For tls-enabled tunneling proxy connections: * If the TLS upgrade fails, verify error propagation and cleanup */ -static int s_test_https_proxy_connection_failure_tls(struct aws_allocator *allocator, void *ctx) { +static int s_test_https_tunnel_proxy_connection_failure_tls(struct aws_allocator *allocator, void *ctx) { (void)ctx; - ASSERT_SUCCESS(s_setup_proxy_test(allocator, PTTM_HTTPS, PTFT_TLS_NEGOTIATION, AWS_HPAT_NONE)); + struct mocked_proxy_test_options options = { + .test_mode = PTTM_HTTPS_TUNNEL, + .failure_type = PTFT_TLS_NEGOTIATION, + }; + + ASSERT_SUCCESS(s_setup_proxy_test(allocator, &options)); ASSERT_SUCCESS(proxy_tester_verify_connection_attempt_was_to_proxy( &tester, aws_byte_cursor_from_c_str(s_proxy_host_name), s_proxy_port)); @@ -323,12 +437,11 @@ static int s_test_https_proxy_connection_failure_tls(struct aws_allocator *alloc return AWS_OP_SUCCESS; } -AWS_TEST_CASE(test_https_proxy_connection_failure_tls, s_test_https_proxy_connection_failure_tls); +AWS_TEST_CASE(test_https_tunnel_proxy_connection_failure_tls, s_test_https_tunnel_proxy_connection_failure_tls); static int s_verify_transformed_request( struct aws_http_message *untransformed_request, struct aws_http_message *transformed_request, - bool used_basic_auth, struct aws_allocator *allocator) { /* method shouldn't change */ @@ -361,21 +474,10 @@ static int s_verify_transformed_request( /* all old headers should still be present */ size_t untransformed_header_count = aws_http_message_get_header_count(untransformed_request); - ASSERT_TRUE( - untransformed_header_count + (used_basic_auth ? 1 : 0) == - aws_http_message_get_header_count(transformed_request)); for (size_t i = 0; i < untransformed_header_count; ++i) { struct aws_http_header header; ASSERT_SUCCESS(aws_http_message_get_header(untransformed_request, &header, i)); - ASSERT_TRUE(s_is_header_in_request(transformed_request, &header)); - } - - /* auth header should be present if basic auth used */ - if (used_basic_auth) { - struct aws_http_header auth_header; - auth_header.name = aws_byte_cursor_from_string(s_expected_auth_header_name); - auth_header.value = aws_byte_cursor_from_string(s_expected_auth_header_value); - ASSERT_TRUE(s_is_header_in_request(transformed_request, &auth_header)); + ASSERT_TRUE(s_is_header_and_value_in_request(transformed_request, &header)); } aws_uri_clean_up(&uri); @@ -383,10 +485,12 @@ static int s_verify_transformed_request( return AWS_OP_SUCCESS; } -static int s_do_http_proxy_request_transform_test(struct aws_allocator *allocator, bool use_basic_auth) { +static int s_do_http_forwarding_proxy_request_transform_test( + struct aws_allocator *allocator, + struct mocked_proxy_test_options *test_options, + int (*transformed_request_verifier_fn)(struct aws_http_message *)) { - ASSERT_SUCCESS( - s_setup_proxy_test(allocator, PTTM_HTTP, PTFT_NONE, use_basic_auth ? AWS_HPAT_BASIC : AWS_HPAT_NONE)); + ASSERT_SUCCESS(s_setup_proxy_test(allocator, test_options)); struct aws_http_message *untransformed_request = s_build_http_request(allocator); struct aws_http_message *request = s_build_http_request(allocator); @@ -401,9 +505,14 @@ static int s_do_http_proxy_request_transform_test(struct aws_allocator *allocato ASSERT_NOT_NULL(stream); aws_http_stream_activate(stream); - testing_channel_run_currently_queued_tasks(tester.testing_channel); + struct testing_channel *channel = proxy_tester_get_current_channel(&tester); + testing_channel_run_currently_queued_tasks(channel); + + s_verify_transformed_request(untransformed_request, request, allocator); - s_verify_transformed_request(untransformed_request, request, use_basic_auth, allocator); + if (transformed_request_verifier_fn != NULL) { + ASSERT_SUCCESS(transformed_request_verifier_fn(request)); + } /* double release the stream because the dummy connection doesn't actually process (and release) it */ aws_http_stream_release(stream); @@ -419,26 +528,502 @@ static int s_do_http_proxy_request_transform_test(struct aws_allocator *allocato /* * If we do pass in proxy options, verify requests get properly transformed */ -static int s_test_http_proxy_request_transform(struct aws_allocator *allocator, void *ctx) { +static int s_test_http_forwarding_proxy_request_transform(struct aws_allocator *allocator, void *ctx) { (void)ctx; - ASSERT_SUCCESS(s_do_http_proxy_request_transform_test(allocator, false)); + struct mocked_proxy_test_options options = { + .test_mode = PTTM_HTTP_FORWARD, + .failure_type = PTFT_NONE, + .proxy_strategy = NULL, + }; + + ASSERT_SUCCESS(s_do_http_forwarding_proxy_request_transform_test(allocator, &options, NULL)); + + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE(test_http_forwarding_proxy_request_transform, s_test_http_forwarding_proxy_request_transform); + +static int s_check_for_basic_auth_header(struct aws_http_message *transformed_request) { + /* Check for basic auth header */ + struct aws_http_header auth_header; + auth_header.name = aws_byte_cursor_from_string(s_expected_basic_auth_header_name); + auth_header.value = aws_byte_cursor_from_string(s_expected_basic_auth_header_value); + ASSERT_TRUE(s_is_header_and_value_in_request(transformed_request, &auth_header)); return AWS_OP_SUCCESS; } -AWS_TEST_CASE(test_http_proxy_request_transform, s_test_http_proxy_request_transform); /* * If we do pass in proxy options, verify requests get properly transformed with basic authentication */ -static int s_test_http_proxy_request_transform_basic_auth(struct aws_allocator *allocator, void *ctx) { +static int s_test_http_forwarding_proxy_request_transform_basic_auth(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct aws_http_proxy_strategy_basic_auth_options config = { + .proxy_connection_type = AWS_HPCT_HTTP_FORWARD, + .user_name = aws_byte_cursor_from_string(s_mock_request_username), + .password = aws_byte_cursor_from_string(s_mock_request_password), + }; + + struct aws_http_proxy_strategy *proxy_strategy = aws_http_proxy_strategy_new_basic_auth(allocator, &config); + + struct mocked_proxy_test_options options = { + .test_mode = PTTM_HTTP_FORWARD, + .failure_type = PTFT_NONE, + .proxy_strategy = proxy_strategy, + }; + + ASSERT_SUCCESS( + s_do_http_forwarding_proxy_request_transform_test(allocator, &options, s_check_for_basic_auth_header)); + + aws_http_proxy_strategy_release(proxy_strategy); + + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE( + test_http_forwarding_proxy_request_transform_basic_auth, + s_test_http_forwarding_proxy_request_transform_basic_auth); + +static int s_test_http_forwarding_proxy_request_transform_legacy_basic_auth( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + struct mocked_proxy_test_options options = { + .test_mode = PTTM_HTTP_FORWARD, + .failure_type = PTFT_NONE, + .auth_type = AWS_HPAT_BASIC, + .legacy_basic_username = aws_byte_cursor_from_string(s_mock_request_username), + .legacy_basic_password = aws_byte_cursor_from_string(s_mock_request_password), + }; + + ASSERT_SUCCESS( + s_do_http_forwarding_proxy_request_transform_test(allocator, &options, s_check_for_basic_auth_header)); + + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE( + test_http_forwarding_proxy_request_transform_legacy_basic_auth, + s_test_http_forwarding_proxy_request_transform_legacy_basic_auth); + +AWS_STATIC_STRING_FROM_LITERAL(s_mock_kerberos_token_value, "abcdefABCDEF123"); + +static struct aws_string *s_mock_aws_http_proxy_negotiation_kerberos_get_token_sync_fn( + void *user_data, + int *out_error_code) { + + struct aws_allocator *allocator = user_data; + + *out_error_code = AWS_ERROR_SUCCESS; + return aws_string_new_from_string(allocator, s_mock_kerberos_token_value); +} + +AWS_STATIC_STRING_FROM_LITERAL(s_expected_auth_header_name, "Proxy-Authorization"); +AWS_STATIC_STRING_FROM_LITERAL(s_expected_kerberos_auth_header_value, "Negotiate abcdefABCDEF123"); + +static int s_verify_kerberos_connect_request(struct aws_http_message *request) { + /* Check for auth header */ + struct aws_http_header auth_header; + auth_header.name = aws_byte_cursor_from_string(s_expected_auth_header_name); + auth_header.value = aws_byte_cursor_from_string(s_expected_kerberos_auth_header_value); + ASSERT_TRUE(s_is_header_and_value_in_request(request, &auth_header)); + + return AWS_OP_SUCCESS; +} + +/* + * Verify requests get properly transformed with kerberos strategy + */ +static int s_test_http_proxy_request_transform_kerberos(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct aws_http_proxy_strategy_tunneling_kerberos_options config = { + .get_token = s_mock_aws_http_proxy_negotiation_kerberos_get_token_sync_fn, + .get_token_user_data = allocator, + }; + + struct aws_http_proxy_strategy *kerberos_strategy = + aws_http_proxy_strategy_new_tunneling_kerberos(allocator, &config); + + struct mocked_proxy_test_options options = { + .test_mode = PTTM_HTTP_TUNNEL, + .failure_type = PTFT_NONE, + .proxy_strategy = kerberos_strategy, + }; + + ASSERT_SUCCESS(s_setup_proxy_test(allocator, &options)); + + ASSERT_SUCCESS(proxy_tester_verify_connection_attempt_was_to_proxy( + &tester, aws_byte_cursor_from_c_str(s_proxy_host_name), s_proxy_port)); + ASSERT_TRUE(tester.client_connection != NULL); + ASSERT_TRUE(tester.wait_result == AWS_ERROR_SUCCESS); + + ASSERT_INT_EQUALS(1, aws_array_list_length(&tester.connect_requests)); + + struct aws_http_message *connect_request = NULL; + aws_array_list_get_at(&tester.connect_requests, &connect_request, 0); + + ASSERT_SUCCESS(s_verify_kerberos_connect_request(connect_request)); + + aws_http_proxy_strategy_release(kerberos_strategy); + + ASSERT_SUCCESS(proxy_tester_clean_up(&tester)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(test_http_proxy_request_transform_kerberos, s_test_http_proxy_request_transform_kerberos); + +static struct aws_string *s_mock_aws_http_proxy_negotiation_kerberos_get_token_sync_failure_fn( + void *user_data, + int *out_error_code) { + + (void)user_data; + + *out_error_code = AWS_ERROR_UNKNOWN; + + return NULL; +} + +static int s_test_http_proxy_kerberos_token_failure(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct aws_http_proxy_strategy_tunneling_kerberos_options config = { + .get_token = s_mock_aws_http_proxy_negotiation_kerberos_get_token_sync_failure_fn, + .get_token_user_data = NULL, + }; + + struct aws_http_proxy_strategy *kerberos_strategy = + aws_http_proxy_strategy_new_tunneling_kerberos(allocator, &config); + + struct mocked_proxy_test_options options = { + .test_mode = PTTM_HTTP_TUNNEL, + .failure_type = PTFT_PROXY_STRATEGY, + .proxy_strategy = kerberos_strategy, + }; + + ASSERT_SUCCESS(s_setup_proxy_test(allocator, &options)); + + ASSERT_SUCCESS(proxy_tester_verify_connection_attempt_was_to_proxy( + &tester, aws_byte_cursor_from_c_str(s_proxy_host_name), s_proxy_port)); + ASSERT_TRUE(tester.client_connection == NULL); + ASSERT_TRUE(tester.wait_result == AWS_ERROR_UNKNOWN); + + aws_http_proxy_strategy_release(kerberos_strategy); + + ASSERT_SUCCESS(proxy_tester_clean_up(&tester)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(test_http_proxy_kerberos_token_failure, s_test_http_proxy_kerberos_token_failure); + +static int s_test_http_proxy_kerberos_connect_failure(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct aws_http_proxy_strategy_tunneling_kerberos_options config = { + .get_token = s_mock_aws_http_proxy_negotiation_kerberos_get_token_sync_fn, + .get_token_user_data = allocator, + }; + + struct aws_http_proxy_strategy *kerberos_strategy = + aws_http_proxy_strategy_new_tunneling_kerberos(allocator, &config); + + struct mocked_proxy_test_options options = { + .test_mode = PTTM_HTTP_TUNNEL, + .failure_type = PTFT_CONNECT_REQUEST, + .proxy_strategy = kerberos_strategy, + }; + + ASSERT_SUCCESS(s_setup_proxy_test(allocator, &options)); + + ASSERT_SUCCESS(proxy_tester_verify_connection_attempt_was_to_proxy( + &tester, aws_byte_cursor_from_c_str(s_proxy_host_name), s_proxy_port)); + ASSERT_TRUE(tester.client_connection == NULL); + ASSERT_TRUE(tester.wait_result == AWS_ERROR_HTTP_PROXY_CONNECT_FAILED); + + aws_http_proxy_strategy_release(kerberos_strategy); + + ASSERT_SUCCESS(proxy_tester_clean_up(&tester)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(test_http_proxy_kerberos_connect_failure, s_test_http_proxy_kerberos_connect_failure); + +AWS_STATIC_STRING_FROM_LITERAL(s_mock_ntlm_token_value, "NTLM_TOKEN"); +AWS_STATIC_STRING_FROM_LITERAL(s_mock_ntlm_challenge_token_value, "NTLM_CHALLENGE_TOKEN"); + +static struct aws_string *s_mock_aws_http_proxy_negotiation_ntlm_get_challenge_token_sync_fn( + void *user_data, + const struct aws_byte_cursor *challenge_value, + int *out_error_code) { + + (void)challenge_value; + + struct aws_allocator *allocator = user_data; + + *out_error_code = AWS_ERROR_SUCCESS; + return aws_string_new_from_string(allocator, s_mock_ntlm_challenge_token_value); +} + +static struct aws_string *s_mock_aws_http_proxy_negotiation_ntlm_get_token_sync_fn( + void *user_data, + int *out_error_code) { + struct aws_allocator *allocator = user_data; + + *out_error_code = AWS_ERROR_SUCCESS; + return aws_string_new_from_string(allocator, s_mock_ntlm_token_value); +} + +static int s_verify_identity_connect_request(struct aws_http_message *request) { + ASSERT_FALSE(s_is_header_in_request(request, aws_byte_cursor_from_string(s_expected_auth_header_name))); + + return AWS_OP_SUCCESS; +} + +static struct aws_http_proxy_strategy *s_create_adaptive_strategy(struct aws_allocator *allocator) { + struct aws_http_proxy_strategy_tunneling_kerberos_options kerberos_config = { + .get_token = s_mock_aws_http_proxy_negotiation_kerberos_get_token_sync_fn, + .get_token_user_data = allocator, + }; + + struct aws_http_proxy_strategy_tunneling_ntlm_options ntlm_config = { + .get_token = s_mock_aws_http_proxy_negotiation_ntlm_get_token_sync_fn, + .get_challenge_token = s_mock_aws_http_proxy_negotiation_ntlm_get_challenge_token_sync_fn, + .get_challenge_token_user_data = allocator, + }; + + struct aws_http_proxy_strategy_tunneling_adaptive_options config = { + .ntlm_options = &ntlm_config, + .kerberos_options = &kerberos_config, + }; + + return aws_http_proxy_strategy_new_tunneling_adaptive(allocator, &config); +} + +static int s_test_http_proxy_adaptive_identity_success(struct aws_allocator *allocator, void *ctx) { (void)ctx; - ASSERT_SUCCESS(s_do_http_proxy_request_transform_test(allocator, true)); + struct aws_http_proxy_strategy *adaptive_strategy = s_create_adaptive_strategy(allocator); + + struct mocked_proxy_test_options options = { + .test_mode = PTTM_HTTP_TUNNEL, + .failure_type = PTFT_NONE, + .proxy_strategy = adaptive_strategy, + }; + + ASSERT_SUCCESS(s_setup_proxy_test(allocator, &options)); + + ASSERT_SUCCESS(proxy_tester_verify_connection_attempt_was_to_proxy( + &tester, aws_byte_cursor_from_c_str(s_proxy_host_name), s_proxy_port)); + ASSERT_TRUE(tester.client_connection != NULL); + ASSERT_TRUE(tester.wait_result == AWS_ERROR_SUCCESS); + + ASSERT_INT_EQUALS(1, aws_array_list_length(&tester.connect_requests)); + + struct aws_http_message *connect_request = NULL; + aws_array_list_get_at(&tester.connect_requests, &connect_request, 0); + + ASSERT_SUCCESS(s_verify_identity_connect_request(connect_request)); + + aws_http_proxy_strategy_release(adaptive_strategy); + + ASSERT_SUCCESS(proxy_tester_clean_up(&tester)); return AWS_OP_SUCCESS; } -AWS_TEST_CASE(test_http_proxy_request_transform_basic_auth, s_test_http_proxy_request_transform_basic_auth); + +AWS_TEST_CASE(test_http_proxy_adaptive_identity_success, s_test_http_proxy_adaptive_identity_success); + +AWS_STATIC_STRING_FROM_LITERAL(s_unauthorized_response, "HTTP/1.0 407 Unauthorized\r\n\r\n"); +AWS_STATIC_STRING_FROM_LITERAL(s_good_response, "HTTP/1.0 200 Connection established\r\nconnection: close\r\n\r\n"); + +typedef int (*aws_proxy_test_verify_connect_fn)(struct aws_http_message *); + +static int s_verify_connect_requests(aws_proxy_test_verify_connect_fn verify_functions[], size_t function_count) { + size_t connect_requests = aws_array_list_length(&tester.connect_requests); + ASSERT_INT_EQUALS(function_count, connect_requests); + + for (size_t i = 0; i < connect_requests; ++i) { + struct aws_http_message *request = NULL; + aws_array_list_get_at(&tester.connect_requests, &request, i); + + ASSERT_SUCCESS(verify_functions[i](request)); + } + + return AWS_OP_SUCCESS; +} + +static int s_test_http_proxy_adaptive_kerberos_success(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct aws_http_proxy_strategy *adaptive_strategy = s_create_adaptive_strategy(allocator); + + struct aws_byte_cursor first_response = aws_byte_cursor_from_string(s_unauthorized_response); + struct aws_byte_cursor second_response = aws_byte_cursor_from_string(s_good_response); + + struct aws_byte_cursor connect_responses[] = { + first_response, + second_response, + }; + + aws_proxy_test_verify_connect_fn verifiers[] = { + s_verify_identity_connect_request, + s_verify_kerberos_connect_request, + }; + + struct mocked_proxy_test_options options = { + .test_mode = PTTM_HTTP_TUNNEL, + .failure_type = PTFT_NONE, + .proxy_strategy = adaptive_strategy, + .mocked_response_count = 2, + .mocked_responses = connect_responses, + }; + + ASSERT_SUCCESS(s_setup_proxy_test(allocator, &options)); + + ASSERT_SUCCESS(proxy_tester_wait(&tester, proxy_tester_connection_setup_pred)); + + ASSERT_TRUE(tester.client_connection != NULL); + ASSERT_TRUE(tester.wait_result == AWS_ERROR_SUCCESS); + + s_verify_connect_requests(verifiers, 2); + + aws_http_proxy_strategy_release(adaptive_strategy); + + ASSERT_SUCCESS(proxy_tester_clean_up(&tester)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(test_http_proxy_adaptive_kerberos_success, s_test_http_proxy_adaptive_kerberos_success); + +AWS_STATIC_STRING_FROM_LITERAL(s_expected_ntlm_token_auth_header_value, "NTLM NTLM_TOKEN"); + +static int s_verify_ntlm_connect_token_request(struct aws_http_message *request) { + /* Check for auth header */ + struct aws_http_header auth_header; + auth_header.name = aws_byte_cursor_from_string(s_expected_auth_header_name); + auth_header.value = aws_byte_cursor_from_string(s_expected_ntlm_token_auth_header_value); + ASSERT_TRUE(s_is_header_and_value_in_request(request, &auth_header)); + + return AWS_OP_SUCCESS; +} + +AWS_STATIC_STRING_FROM_LITERAL(s_expected_ntlm_challenge_token_auth_header_value, "NTLM NTLM_CHALLENGE_TOKEN"); + +static int s_verify_ntlm_connect_challenge_token_request(struct aws_http_message *request) { + /* Check for auth header */ + struct aws_http_header auth_header; + auth_header.name = aws_byte_cursor_from_string(s_expected_auth_header_name); + auth_header.value = aws_byte_cursor_from_string(s_expected_ntlm_challenge_token_auth_header_value); + ASSERT_TRUE(s_is_header_and_value_in_request(request, &auth_header)); + + return AWS_OP_SUCCESS; +} + +AWS_STATIC_STRING_FROM_LITERAL(s_ntlm_response, "HTTP/1.0 407 Bad\r\nProxy-Authenticate: TestChallenge\r\n\r\n"); + +static int s_test_http_proxy_adaptive_ntlm_success(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct aws_http_proxy_strategy *adaptive_strategy = s_create_adaptive_strategy(allocator); + + struct aws_byte_cursor bad_response = aws_byte_cursor_from_string(s_ntlm_response); + struct aws_byte_cursor good_response = aws_byte_cursor_from_string(s_good_response); + + struct aws_byte_cursor connect_responses[] = { + bad_response, + bad_response, + bad_response, + good_response, + }; + + aws_proxy_test_verify_connect_fn verifiers[] = { + s_verify_identity_connect_request, + s_verify_kerberos_connect_request, + s_verify_ntlm_connect_token_request, + s_verify_ntlm_connect_challenge_token_request, + }; + + struct mocked_proxy_test_options options = { + .test_mode = PTTM_HTTP_TUNNEL, + .failure_type = PTFT_NONE, + .proxy_strategy = adaptive_strategy, + .mocked_response_count = 4, + .mocked_responses = connect_responses, + }; + + ASSERT_SUCCESS(s_setup_proxy_test(allocator, &options)); + + ASSERT_SUCCESS(proxy_tester_verify_connect_request(&tester)); + ASSERT_SUCCESS(proxy_tester_send_connect_response(&tester)); + + ASSERT_SUCCESS(proxy_tester_wait(&tester, proxy_tester_connection_setup_pred)); + + ASSERT_TRUE(tester.client_connection != NULL); + ASSERT_TRUE(tester.wait_result == AWS_ERROR_SUCCESS); + + ASSERT_SUCCESS(s_verify_connect_requests(verifiers, 4)); + + aws_http_proxy_strategy_release(adaptive_strategy); + + ASSERT_SUCCESS(proxy_tester_clean_up(&tester)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(test_http_proxy_adaptive_ntlm_success, s_test_http_proxy_adaptive_ntlm_success); + +static int s_test_http_proxy_adaptive_failure(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct aws_http_proxy_strategy *adaptive_strategy = s_create_adaptive_strategy(allocator); + + struct aws_byte_cursor bad_response = aws_byte_cursor_from_string(s_ntlm_response); + + struct aws_byte_cursor connect_responses[] = { + bad_response, + bad_response, + bad_response, + bad_response, + }; + + aws_proxy_test_verify_connect_fn verifiers[] = { + s_verify_identity_connect_request, + s_verify_kerberos_connect_request, + s_verify_ntlm_connect_token_request, + s_verify_ntlm_connect_challenge_token_request, + }; + + struct mocked_proxy_test_options options = { + .test_mode = PTTM_HTTP_TUNNEL, + .failure_type = PTFT_NONE, + .proxy_strategy = adaptive_strategy, + .mocked_response_count = 4, + .mocked_responses = connect_responses, + }; + + ASSERT_SUCCESS(s_setup_proxy_test(allocator, &options)); + + ASSERT_SUCCESS(proxy_tester_verify_connect_request(&tester)); + ASSERT_SUCCESS(proxy_tester_send_connect_response(&tester)); + + ASSERT_SUCCESS(proxy_tester_wait(&tester, proxy_tester_connection_setup_pred)); + + ASSERT_TRUE(tester.wait_result == AWS_ERROR_HTTP_PROXY_CONNECT_FAILED); + + ASSERT_SUCCESS(s_verify_connect_requests(verifiers, 4)); + + aws_http_proxy_strategy_release(adaptive_strategy); + + ASSERT_SUCCESS(proxy_tester_clean_up(&tester)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(test_http_proxy_adaptive_failure, s_test_http_proxy_adaptive_failure); AWS_STATIC_STRING_FROM_LITERAL(s_rewrite_host, "www.uri.com"); AWS_STATIC_STRING_FROM_LITERAL(s_rewrite_path, "/main/index.html?foo=bar"); @@ -490,7 +1075,7 @@ static int s_do_request_rewrite_test( return AWS_OP_SUCCESS; } -static int s_test_http_proxy_uri_rewrite(struct aws_allocator *allocator, void *ctx) { +static int s_test_http_forwarding_proxy_uri_rewrite(struct aws_allocator *allocator, void *ctx) { (void)ctx; ASSERT_SUCCESS(s_do_request_rewrite_test( @@ -498,13 +1083,13 @@ static int s_test_http_proxy_uri_rewrite(struct aws_allocator *allocator, void * return AWS_OP_SUCCESS; } -AWS_TEST_CASE(test_http_proxy_uri_rewrite, s_test_http_proxy_uri_rewrite); +AWS_TEST_CASE(test_http_forwarding_proxy_uri_rewrite, s_test_http_forwarding_proxy_uri_rewrite); AWS_STATIC_STRING_FROM_LITERAL(s_options_request_method, "OPTIONS"); AWS_STATIC_STRING_FROM_LITERAL(s_options_star_path, "*"); AWS_STATIC_STRING_FROM_LITERAL(s_expected_rewritten_options_path, "http://www.uri.com:80"); -static int s_test_http_proxy_uri_rewrite_options_star(struct aws_allocator *allocator, void *ctx) { +static int s_test_http_forwarding_proxy_uri_rewrite_options_star(struct aws_allocator *allocator, void *ctx) { (void)ctx; ASSERT_SUCCESS(s_do_request_rewrite_test( @@ -512,4 +1097,6 @@ static int s_test_http_proxy_uri_rewrite_options_star(struct aws_allocator *allo return AWS_OP_SUCCESS; } -AWS_TEST_CASE(test_http_proxy_uri_rewrite_options_star, s_test_http_proxy_uri_rewrite_options_star); +AWS_TEST_CASE( + test_http_forwarding_proxy_uri_rewrite_options_star, + s_test_http_forwarding_proxy_uri_rewrite_options_star);