blob: 35ba6074e7a3dace9cb5a966ecb8c1f91787f3d4 [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;
92 FRIEND_TEST(ServerProxyTest, HandlePendingJobs);
Andreea Costinas5862b102020-03-19 14:45:36 +010093 FRIEND_TEST(ServerProxyTest, HandleConnectRequest);
Andreea Costinase45d54b2020-03-10 09:21:14 +010094 FRIEND_TEST(ProxyConnectJobTest, SuccessfulConnection);
Andreea Costinased3f9782020-05-20 17:09:46 +020095 FRIEND_TEST(ProxyConnectJobTest, SuccessfulConnectionAltEnding);
Andreea Costinase45d54b2020-03-10 09:21:14 +010096 FRIEND_TEST(ProxyConnectJobTest, BadHttpRequestWrongMethod);
97 FRIEND_TEST(ProxyConnectJobTest, BadHttpRequestNoEmptyLine);
Andreea Costinasbb2aa022020-06-13 00:03:23 +020098 FRIEND_TEST(ProxyConnectJobTest, ResendWithCredentials);
99 FRIEND_TEST(ProxyConnectJobTest, NoCredentials);
100 FRIEND_TEST(ProxyConnectJobTest, KerberosAuth);
Andreea Costinased9e6122020-08-12 12:06:19 +0200101 FRIEND_TEST(ProxyConnectJobTest, AuthenticationTimeout);
Andreea Costinase45d54b2020-03-10 09:21:14 +0100102
103 // Reads data from the socket into |raw_request| until the first empty line,
104 // which would mark the end of the HTTP request header.
105 // This method does not validate the headers.
106 bool TryReadHttpHeader(std::vector<char>* raw_request);
107
108 // Called when the client socket is ready for reading.
109 void OnClientReadReady();
110
111 // Called from |OnProxyResolution|, after the proxy for |target_url_| is
112 // resolved.
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200113 void DoCurlServerConnection();
Andreea Costinase45d54b2020-03-10 09:21:14 +0100114
115 void OnError(const std::string_view& http_error_message);
116
Andreea Costinas08a5d182020-04-29 22:12:47 +0200117 void OnClientConnectTimeout();
118
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200119 // Requests the credentials to authenticate to the remote proxy server. The
120 // credentials are bound to the protection space extracted from the challenge
121 // sent by the remote server.
122 void AuthenticationRequired(const std::vector<char>& http_response_headers);
123 void OnAuthCredentialsProvided(const std::string& credentials);
Andreea Costinased9e6122020-08-12 12:06:19 +0200124 // Proxy credentials are asynchronously requested from the Browser. The user
125 // can ignore the authentication request. This method will forward the
126 // authentication failure message to the client and is triggered when the
127 // waiting time for the credentials has expired.
128 void OnAuthenticationTimeout();
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200129 // Checks if the HTTP CONNECT request has failed because of missing proxy
130 // authentication credentials.
131 bool AreAuthCredentialsRequired(CURL* easyhandle);
132
Andreea Costinasf90a4c02020-06-12 22:30:51 +0200133 // Sends the server response to the client. Returns true if the headers and
134 // body were sent successfully, false otherwise. In case of failure, calls
135 // |OnError|. The response headers and body can be empty if the libcurl
136 // connection fails. In this case, this will send the client an error message
137 // based on the HTTP status code |http_response_code_|.
138 bool SendHttpResponseToClient(const std::vector<char>& http_response_headers,
139 const std::vector<char>& http_response_body);
140
Andreea Costinase45d54b2020-03-10 09:21:14 +0100141 std::string target_url_;
Andreea Costinasf90a4c02020-06-12 22:30:51 +0200142 // HTTP proxy response code to the CONNECT request.
143 int64_t http_response_code_ = 0;
144
Andreea Costinased9e6122020-08-12 12:06:19 +0200145 // Indicates that the timer for waiting for authentication credentials has
146 // started. The timer is started the first time the credentials are requested.
147 // Subsequent authentication attempts will not re-start the timer.
148 bool authentication_timer_started_ = false;
149
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200150 std::string credentials_;
Andreea Costinase45d54b2020-03-10 09:21:14 +0100151 std::list<std::string> proxy_servers_;
152 ResolveProxyCallback resolve_proxy_callback_;
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200153 AuthenticationRequiredCallback auth_required_callback_;
Andreea Costinase45d54b2020-03-10 09:21:14 +0100154 OnConnectionSetupFinishedCallback setup_finished_callback_;
Andreea Costinas08a5d182020-04-29 22:12:47 +0200155 base::CancelableClosure client_connect_timeout_callback_;
Andreea Costinased9e6122020-08-12 12:06:19 +0200156 // Started the first time credentials are requested and cancelled when the
157 // proxy server sends any HTTP code other than 407 (proxy authentication
158 // required).
159 base::CancelableClosure credentials_request_timeout_callback_;
Andreea Costinas08a5d182020-04-29 22:12:47 +0200160
Garrick Evans3388a032020-03-24 11:25:55 +0900161 std::unique_ptr<patchpanel::Socket> client_socket_;
Andreea Costinase45d54b2020-03-10 09:21:14 +0100162 std::unique_ptr<base::FileDescriptorWatcher::Controller> read_watcher_;
Andreea Costinas833eb7c2020-06-12 11:09:15 +0200163 base::WeakPtrFactory<ProxyConnectJob> weak_ptr_factory_{this};
Andreea Costinase45d54b2020-03-10 09:21:14 +0100164};
165} // namespace system_proxy
166
167#endif // SYSTEM_PROXY_PROXY_CONNECT_JOB_H_