blob: b872c1c41445c696f5870b66c04f0d5f9a2430f8 [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,
74 ResolveProxyCallback resolve_proxy_callback,
Andreea Costinasbb2aa022020-06-13 00:03:23 +020075 AuthenticationRequiredCallback auth_required_callback,
Andreea Costinase45d54b2020-03-10 09:21:14 +010076 OnConnectionSetupFinishedCallback setup_finished_callback);
77 ProxyConnectJob(const ProxyConnectJob&) = delete;
78 ProxyConnectJob& operator=(const ProxyConnectJob&) = delete;
79 virtual ~ProxyConnectJob();
80
81 // Marks |client_socket_| as non-blocking and adds a watcher that calls
82 // |OnClientReadReady| when the socket is read ready.
83 virtual bool Start();
84 void OnProxyResolution(const std::list<std::string>& proxy_servers);
85
86 friend std::ostream& operator<<(std::ostream& stream,
87 const ProxyConnectJob& job);
88
89 private:
90 friend class ServerProxyTest;
91 friend class ProxyConnectJobTest;
Andreea Costinasa8bc59c2020-09-18 13:49:13 +020092 friend class HttpServerProxyConnectJobTest;
Andreea Costinase45d54b2020-03-10 09:21:14 +010093 FRIEND_TEST(ServerProxyTest, HandlePendingJobs);
Andreea Costinas5862b102020-03-19 14:45:36 +010094 FRIEND_TEST(ServerProxyTest, HandleConnectRequest);
Andreea Costinase45d54b2020-03-10 09:21:14 +010095 FRIEND_TEST(ProxyConnectJobTest, BadHttpRequestWrongMethod);
96 FRIEND_TEST(ProxyConnectJobTest, BadHttpRequestNoEmptyLine);
Andreea Costinas435851b2020-05-25 14:18:41 +020097 FRIEND_TEST(ProxyConnectJobTest, SlowlorisTimeout);
Andreea Costinasa8bc59c2020-09-18 13:49:13 +020098 FRIEND_TEST(HttpServerProxyConnectJobTest, SuccessfulConnection);
99 FRIEND_TEST(HttpServerProxyConnectJobTest, SuccessfulConnectionAltEnding);
100 FRIEND_TEST(HttpServerProxyConnectJobTest, ResendWithCredentials);
101 FRIEND_TEST(HttpServerProxyConnectJobTest, NoCredentials);
102 FRIEND_TEST(HttpServerProxyConnectJobTest, KerberosAuth);
103 FRIEND_TEST(HttpServerProxyConnectJobTest, AuthenticationTimeout);
Andreea Costinas435851b2020-05-25 14:18:41 +0200104 FRIEND_TEST(HttpServerProxyConnectJobTest, MultipleReadConnectRequest);
105 FRIEND_TEST(HttpServerProxyConnectJobTest, BufferedClientData);
106 FRIEND_TEST(HttpServerProxyConnectJobTest, BufferedClientDataAltEnding);
Andreea Costinase45d54b2020-03-10 09:21:14 +0100107
108 // Called when the client socket is ready for reading.
109 void OnClientReadReady();
110
Andreea Costinas435851b2020-05-25 14:18:41 +0200111 void HandleClientHTTPRequest(const base::StringPiece& http_request);
112
Andreea Costinase45d54b2020-03-10 09:21:14 +0100113 // Called from |OnProxyResolution|, after the proxy for |target_url_| is
114 // resolved.
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200115 void DoCurlServerConnection();
Andreea Costinase45d54b2020-03-10 09:21:14 +0100116
117 void OnError(const std::string_view& http_error_message);
118
Andreea Costinas08a5d182020-04-29 22:12:47 +0200119 void OnClientConnectTimeout();
120
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200121 // Requests the credentials to authenticate to the remote proxy server. The
122 // credentials are bound to the protection space extracted from the challenge
123 // sent by the remote server.
124 void AuthenticationRequired(const std::vector<char>& http_response_headers);
125 void OnAuthCredentialsProvided(const std::string& credentials);
Andreea Costinased9e6122020-08-12 12:06:19 +0200126 // Proxy credentials are asynchronously requested from the Browser. The user
127 // can ignore the authentication request. This method will forward the
128 // authentication failure message to the client and is triggered when the
129 // waiting time for the credentials has expired.
130 void OnAuthenticationTimeout();
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200131 // Checks if the HTTP CONNECT request has failed because of missing proxy
132 // authentication credentials.
133 bool AreAuthCredentialsRequired(CURL* easyhandle);
134
Andreea Costinasf90a4c02020-06-12 22:30:51 +0200135 // Sends the server response to the client. Returns true if the headers and
136 // body were sent successfully, false otherwise. In case of failure, calls
137 // |OnError|. The response headers and body can be empty if the libcurl
138 // connection fails. In this case, this will send the client an error message
139 // based on the HTTP status code |http_response_code_|.
140 bool SendHttpResponseToClient(const std::vector<char>& http_response_headers,
141 const std::vector<char>& http_response_body);
142
Andreea Costinase45d54b2020-03-10 09:21:14 +0100143 std::string target_url_;
Andreea Costinasf90a4c02020-06-12 22:30:51 +0200144 // HTTP proxy response code to the CONNECT request.
145 int64_t http_response_code_ = 0;
146
Andreea Costinased9e6122020-08-12 12:06:19 +0200147 // Indicates that the timer for waiting for authentication credentials has
148 // started. The timer is started the first time the credentials are requested.
149 // Subsequent authentication attempts will not re-start the timer.
150 bool authentication_timer_started_ = false;
151
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200152 std::string credentials_;
Andreea Costinas435851b2020-05-25 14:18:41 +0200153 // CONNECT request and playload data read from the client socket. The client
154 // may send the HTTP CONNECT request across multiple socket writes.
155 // |connect_data_| will cache the partial messages until we receive the end of
156 // the HTTP request. It's also possible that the client will start forwarding
157 // data without waiting for a reply from the server (RFC2817, section-5.2).
158 // Any data read from the client socket which is not part of the HTTP CONNECT
159 // message will be stored in |connect_data_| and forwarded to the remote proxy
160 // after a successful connection is established.
161 std::vector<char> connect_data_;
Andreea Costinase45d54b2020-03-10 09:21:14 +0100162 std::list<std::string> proxy_servers_;
163 ResolveProxyCallback resolve_proxy_callback_;
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200164 AuthenticationRequiredCallback auth_required_callback_;
Andreea Costinase45d54b2020-03-10 09:21:14 +0100165 OnConnectionSetupFinishedCallback setup_finished_callback_;
Andreea Costinas08a5d182020-04-29 22:12:47 +0200166 base::CancelableClosure client_connect_timeout_callback_;
Andreea Costinased9e6122020-08-12 12:06:19 +0200167 // Started the first time credentials are requested and cancelled when the
168 // proxy server sends any HTTP code other than 407 (proxy authentication
169 // required).
170 base::CancelableClosure credentials_request_timeout_callback_;
Andreea Costinas08a5d182020-04-29 22:12:47 +0200171
Garrick Evans3388a032020-03-24 11:25:55 +0900172 std::unique_ptr<patchpanel::Socket> client_socket_;
Andreea Costinase45d54b2020-03-10 09:21:14 +0100173 std::unique_ptr<base::FileDescriptorWatcher::Controller> read_watcher_;
Andreea Costinas833eb7c2020-06-12 11:09:15 +0200174 base::WeakPtrFactory<ProxyConnectJob> weak_ptr_factory_{this};
Andreea Costinase45d54b2020-03-10 09:21:14 +0100175};
176} // namespace system_proxy
177
178#endif // SYSTEM_PROXY_PROXY_CONNECT_JOB_H_