blob: 004093c488af535db87654c6bcaea2812418bd4a [file] [log] [blame]
Andreea Costinase45d54b2020-03-10 09:21:14 +01001// Copyright 2020 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4#ifndef SYSTEM_PROXY_PROXY_CONNECT_JOB_H_
5#define SYSTEM_PROXY_PROXY_CONNECT_JOB_H_
6
7#include <list>
8#include <memory>
9#include <string>
10#include <string_view>
11#include <vector>
12
Andreea Costinasf90a4c02020-06-12 22:30:51 +020013#include <curl/curl.h>
14
Andreea Costinase45d54b2020-03-10 09:21:14 +010015#include <base/callback_forward.h>
Andreea Costinas08a5d182020-04-29 22:12:47 +020016#include <base/cancelable_callback.h>
Andreea Costinase45d54b2020-03-10 09:21:14 +010017#include <base/files/file_descriptor_watcher_posix.h>
18#include <gtest/gtest_prod.h> // for FRIEND_TEST
19
Garrick Evans3388a032020-03-24 11:25:55 +090020namespace patchpanel {
Andreea Costinase45d54b2020-03-10 09:21:14 +010021class SocketForwarder;
22class Socket;
Garrick Evans3388a032020-03-24 11:25:55 +090023} // namespace patchpanel
Andreea Costinase45d54b2020-03-10 09:21:14 +010024
25namespace system_proxy {
26// ProxyConnectJob asynchronously sets up a connection to a remote target on
Andreea Costinasbb2aa022020-06-13 00:03:23 +020027// behalf of a client using the following steps:
28// 1. Gets the target url from the client request;
29// 2. Asks the parent to resolve the proxy for the target url via
30// |resolve_proxy_callback_|;
31// 3. Connects to the target url trough the remote proxy server returned by the
32// parent.
33// 3. 1. On success, it will return a SocketForwarder to the parent, which
34// forwards data between the Chrome OS client and the remote server.
35// 3. 2. On error, it will check the HTTP status code from the server's reply.
36// 3. 2. 1. If the status code means credentials are required, it asks the
37// parent for authentication credentials via |auth_required_callback|.
38// - If the parent returns credentials for the proxy server challenge,
39// it attempts to reconnect (step 3);
40// - Otherwise it will forward the status code to the client.
41// 3.2.1.2. Other status codes are forwarded to the Chrome OS clients and the
42// connection is closed.
43// Note: Reconnecting to the server with credentials (step 3. 2. 1.) will create
44// a new connection to the remote server, while the connection to the local
45// client is still open and in a waiting state during the authentication
46// process.
47// TODO(acostinas,crbug.com/1098200): Cancel the proxy connect job if the
48// request for credentials is not resolved after a certain time.
Andreea Costinase45d54b2020-03-10 09:21:14 +010049class ProxyConnectJob {
50 public:
51 using OnConnectionSetupFinishedCallback = base::OnceCallback<void(
Garrick Evans3388a032020-03-24 11:25:55 +090052 std::unique_ptr<patchpanel::SocketForwarder>, ProxyConnectJob*)>;
Andreea Costinase45d54b2020-03-10 09:21:14 +010053
54 // Will be invoked by ProxyConnectJob to resolve the proxy for |target_url_|.
55 // The passed |callback| is expected to be called with the list of proxy
56 // servers, which will always contain at least one entry, the default proxy.
57 using ResolveProxyCallback = base::OnceCallback<void(
58 const std::string& url,
Andreea Costinasbb2aa022020-06-13 00:03:23 +020059 base::OnceCallback<void(const std::list<std::string>&)>
60 on_proxy_resolution_callback)>;
61 // Will be invoked by ProxyConnectJob to request the credentials for requests
Andreea Costinased9e6122020-08-12 12:06:19 +020062 // that fail with code 407. If |bad_cached_credentials| is true, the
63 // credentials previously acquired for proxy authentication are incorrect.
64 using AuthenticationRequiredCallback = base::RepeatingCallback<void(
Andreea Costinasbb2aa022020-06-13 00:03:23 +020065 const std::string& proxy_url,
66 const std::string& scheme,
67 const std::string& realm,
Andreea Costinased9e6122020-08-12 12:06:19 +020068 const std::string& bad_cached_credentials,
69 base::RepeatingCallback<void(const std::string& credentials)>
Andreea Costinasbb2aa022020-06-13 00:03:23 +020070 on_auth_acquired_callback)>;
Andreea Costinase45d54b2020-03-10 09:21:14 +010071
Garrick Evans3388a032020-03-24 11:25:55 +090072 ProxyConnectJob(std::unique_ptr<patchpanel::Socket> socket,
Andreea Costinase45d54b2020-03-10 09:21:14 +010073 const std::string& credentials,
Andreea Costinascc4d54e2020-10-19 15:46:25 +020074 int64_t curl_auth_schemes,
Andreea Costinase45d54b2020-03-10 09:21:14 +010075 ResolveProxyCallback resolve_proxy_callback,
Andreea Costinasbb2aa022020-06-13 00:03:23 +020076 AuthenticationRequiredCallback auth_required_callback,
Andreea Costinase45d54b2020-03-10 09:21:14 +010077 OnConnectionSetupFinishedCallback setup_finished_callback);
78 ProxyConnectJob(const ProxyConnectJob&) = delete;
79 ProxyConnectJob& operator=(const ProxyConnectJob&) = delete;
80 virtual ~ProxyConnectJob();
81
82 // Marks |client_socket_| as non-blocking and adds a watcher that calls
83 // |OnClientReadReady| when the socket is read ready.
84 virtual bool Start();
85 void OnProxyResolution(const std::list<std::string>& proxy_servers);
86
Andreea Costinascc4d54e2020-10-19 15:46:25 +020087 // Enables storing curl debug information for test.
88 void StoreRequestHeadersForTesting();
89 // Returns the HTTP headers sent by the curl client. Tests must call
90 // `StoreRequestHeadersForTesting` before calling this method.
91 std::string GetRequestHeadersForTesting();
92
Andreea Costinase45d54b2020-03-10 09:21:14 +010093 friend std::ostream& operator<<(std::ostream& stream,
94 const ProxyConnectJob& job);
95
96 private:
97 friend class ServerProxyTest;
98 friend class ProxyConnectJobTest;
Andreea Costinasa8bc59c2020-09-18 13:49:13 +020099 friend class HttpServerProxyConnectJobTest;
Andreea Costinase45d54b2020-03-10 09:21:14 +0100100 FRIEND_TEST(ServerProxyTest, HandlePendingJobs);
Andreea Costinas5862b102020-03-19 14:45:36 +0100101 FRIEND_TEST(ServerProxyTest, HandleConnectRequest);
Andreea Costinase45d54b2020-03-10 09:21:14 +0100102 FRIEND_TEST(ProxyConnectJobTest, BadHttpRequestWrongMethod);
103 FRIEND_TEST(ProxyConnectJobTest, BadHttpRequestNoEmptyLine);
Andreea Costinas435851b2020-05-25 14:18:41 +0200104 FRIEND_TEST(ProxyConnectJobTest, SlowlorisTimeout);
Andreea Costinasa8bc59c2020-09-18 13:49:13 +0200105 FRIEND_TEST(HttpServerProxyConnectJobTest, SuccessfulConnection);
106 FRIEND_TEST(HttpServerProxyConnectJobTest, SuccessfulConnectionAltEnding);
107 FRIEND_TEST(HttpServerProxyConnectJobTest, ResendWithCredentials);
108 FRIEND_TEST(HttpServerProxyConnectJobTest, NoCredentials);
109 FRIEND_TEST(HttpServerProxyConnectJobTest, KerberosAuth);
110 FRIEND_TEST(HttpServerProxyConnectJobTest, AuthenticationTimeout);
Andreea Costinas435851b2020-05-25 14:18:41 +0200111 FRIEND_TEST(HttpServerProxyConnectJobTest, MultipleReadConnectRequest);
112 FRIEND_TEST(HttpServerProxyConnectJobTest, BufferedClientData);
113 FRIEND_TEST(HttpServerProxyConnectJobTest, BufferedClientDataAltEnding);
Andreea Costinascc4d54e2020-10-19 15:46:25 +0200114 FRIEND_TEST(HttpServerProxyConnectJobTest, PolicyAuthSchemeOk);
115 FRIEND_TEST(HttpServerProxyConnectJobTest, PolicyAuthBadScheme);
Andreea Costinase45d54b2020-03-10 09:21:14 +0100116
117 // Called when the client socket is ready for reading.
118 void OnClientReadReady();
119
Andreea Costinas435851b2020-05-25 14:18:41 +0200120 void HandleClientHTTPRequest(const base::StringPiece& http_request);
121
Andreea Costinase45d54b2020-03-10 09:21:14 +0100122 // Called from |OnProxyResolution|, after the proxy for |target_url_| is
123 // resolved.
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200124 void DoCurlServerConnection();
Andreea Costinase45d54b2020-03-10 09:21:14 +0100125
126 void OnError(const std::string_view& http_error_message);
127
Andreea Costinas08a5d182020-04-29 22:12:47 +0200128 void OnClientConnectTimeout();
129
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200130 // Requests the credentials to authenticate to the remote proxy server. The
131 // credentials are bound to the protection space extracted from the challenge
132 // sent by the remote server.
133 void AuthenticationRequired(const std::vector<char>& http_response_headers);
134 void OnAuthCredentialsProvided(const std::string& credentials);
Andreea Costinased9e6122020-08-12 12:06:19 +0200135 // Proxy credentials are asynchronously requested from the Browser. The user
136 // can ignore the authentication request. This method will forward the
137 // authentication failure message to the client and is triggered when the
138 // waiting time for the credentials has expired.
139 void OnAuthenticationTimeout();
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200140 // Checks if the HTTP CONNECT request has failed because of missing proxy
141 // authentication credentials.
142 bool AreAuthCredentialsRequired(CURL* easyhandle);
143
Andreea Costinasf90a4c02020-06-12 22:30:51 +0200144 // Sends the server response to the client. Returns true if the headers and
145 // body were sent successfully, false otherwise. In case of failure, calls
146 // |OnError|. The response headers and body can be empty if the libcurl
147 // connection fails. In this case, this will send the client an error message
148 // based on the HTTP status code |http_response_code_|.
149 bool SendHttpResponseToClient(const std::vector<char>& http_response_headers,
150 const std::vector<char>& http_response_body);
151
Andreea Costinase45d54b2020-03-10 09:21:14 +0100152 std::string target_url_;
Andreea Costinasf90a4c02020-06-12 22:30:51 +0200153 // HTTP proxy response code to the CONNECT request.
154 int64_t http_response_code_ = 0;
155
Andreea Costinased9e6122020-08-12 12:06:19 +0200156 // Indicates that the timer for waiting for authentication credentials has
157 // started. The timer is started the first time the credentials are requested.
158 // Subsequent authentication attempts will not re-start the timer.
159 bool authentication_timer_started_ = false;
160
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200161 std::string credentials_;
Andreea Costinascc4d54e2020-10-19 15:46:25 +0200162 // Bitmask to tell curl which authentication methods are allowed to be used
163 // with `credentials_`. This value is set with the `CURLOPT_PROXYAUTH` option.
164 int64_t curl_auth_schemes_;
Andreea Costinas435851b2020-05-25 14:18:41 +0200165 // CONNECT request and playload data read from the client socket. The client
166 // may send the HTTP CONNECT request across multiple socket writes.
167 // |connect_data_| will cache the partial messages until we receive the end of
168 // the HTTP request. It's also possible that the client will start forwarding
169 // data without waiting for a reply from the server (RFC2817, section-5.2).
170 // Any data read from the client socket which is not part of the HTTP CONNECT
171 // message will be stored in |connect_data_| and forwarded to the remote proxy
172 // after a successful connection is established.
173 std::vector<char> connect_data_;
Andreea Costinase45d54b2020-03-10 09:21:14 +0100174 std::list<std::string> proxy_servers_;
175 ResolveProxyCallback resolve_proxy_callback_;
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200176 AuthenticationRequiredCallback auth_required_callback_;
Andreea Costinase45d54b2020-03-10 09:21:14 +0100177 OnConnectionSetupFinishedCallback setup_finished_callback_;
Andreea Costinas08a5d182020-04-29 22:12:47 +0200178 base::CancelableClosure client_connect_timeout_callback_;
Andreea Costinased9e6122020-08-12 12:06:19 +0200179 // Started the first time credentials are requested and cancelled when the
180 // proxy server sends any HTTP code other than 407 (proxy authentication
181 // required).
182 base::CancelableClosure credentials_request_timeout_callback_;
Andreea Costinas08a5d182020-04-29 22:12:47 +0200183
Andreea Costinascc4d54e2020-10-19 15:46:25 +0200184 bool store_headers_for_testing_ = false;
185 std::string request_headers_for_testing_;
186
Garrick Evans3388a032020-03-24 11:25:55 +0900187 std::unique_ptr<patchpanel::Socket> client_socket_;
Andreea Costinase45d54b2020-03-10 09:21:14 +0100188 std::unique_ptr<base::FileDescriptorWatcher::Controller> read_watcher_;
Andreea Costinas833eb7c2020-06-12 11:09:15 +0200189 base::WeakPtrFactory<ProxyConnectJob> weak_ptr_factory_{this};
Andreea Costinase45d54b2020-03-10 09:21:14 +0100190};
191} // namespace system_proxy
192
193#endif // SYSTEM_PROXY_PROXY_CONNECT_JOB_H_