1 // Copyright (c) 2012 The Chromium 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 5 #include "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h" 6 7 #include "base/bind.h" 8 #include "base/bind_helpers.h" 9 #include "base/logging.h" 10 #include "base/message_loop/message_loop_proxy.h" 11 #include "base/metrics/histogram.h" 12 #include "base/strings/string_util.h" 13 #include "base/strings/sys_string_conversions.h" 14 #include "base/task_runner.h" 15 #include "base/time/time.h" 16 #include "net/base/net_errors.h" 17 #include "net/proxy/dhcpcsvc_init_win.h" 18 #include "net/proxy/proxy_script_fetcher_impl.h" 19 #include "net/url_request/url_request_context.h" 20 21 #include <windows.h> 22 #include <winsock2.h> 23 #include <dhcpcsdk.h> 24 #pragma comment(lib, "dhcpcsvc.lib") 25 26 namespace { 27 28 // Maximum amount of time to wait for response from the Win32 DHCP API. 29 const int kTimeoutMs = 2000; 30 31 } // namespace 32 33 namespace net { 34 35 DhcpProxyScriptAdapterFetcher::DhcpProxyScriptAdapterFetcher( 36 URLRequestContext* url_request_context, 37 scoped_refptr<base::TaskRunner> task_runner) 38 : task_runner_(task_runner), 39 state_(STATE_START), 40 result_(ERR_IO_PENDING), 41 url_request_context_(url_request_context) { 42 DCHECK(url_request_context_); 43 } 44 45 DhcpProxyScriptAdapterFetcher::~DhcpProxyScriptAdapterFetcher() { 46 Cancel(); 47 } 48 49 void DhcpProxyScriptAdapterFetcher::Fetch( 50 const std::string& adapter_name, const CompletionCallback& callback) { 51 DCHECK(CalledOnValidThread()); 52 DCHECK_EQ(state_, STATE_START); 53 result_ = ERR_IO_PENDING; 54 pac_script_ = base::string16(); 55 state_ = STATE_WAIT_DHCP; 56 callback_ = callback; 57 58 wait_timer_.Start(FROM_HERE, ImplGetTimeout(), 59 this, &DhcpProxyScriptAdapterFetcher::OnTimeout); 60 scoped_refptr<DhcpQuery> dhcp_query(ImplCreateDhcpQuery()); 61 task_runner_->PostTaskAndReply( 62 FROM_HERE, 63 base::Bind( 64 &DhcpProxyScriptAdapterFetcher::DhcpQuery::GetPacURLForAdapter, 65 dhcp_query.get(), 66 adapter_name), 67 base::Bind( 68 &DhcpProxyScriptAdapterFetcher::OnDhcpQueryDone, 69 AsWeakPtr(), 70 dhcp_query)); 71 } 72 73 void DhcpProxyScriptAdapterFetcher::Cancel() { 74 DCHECK(CalledOnValidThread()); 75 callback_.Reset(); 76 wait_timer_.Stop(); 77 script_fetcher_.reset(); 78 79 switch (state_) { 80 case STATE_WAIT_DHCP: 81 // Nothing to do here, we let the worker thread run to completion, 82 // the task it posts back when it completes will check the state. 83 break; 84 case STATE_WAIT_URL: 85 break; 86 case STATE_START: 87 case STATE_FINISH: 88 case STATE_CANCEL: 89 break; 90 } 91 92 if (state_ != STATE_FINISH) { 93 result_ = ERR_ABORTED; 94 state_ = STATE_CANCEL; 95 } 96 } 97 98 bool DhcpProxyScriptAdapterFetcher::DidFinish() const { 99 DCHECK(CalledOnValidThread()); 100 return state_ == STATE_FINISH; 101 } 102 103 int DhcpProxyScriptAdapterFetcher::GetResult() const { 104 DCHECK(CalledOnValidThread()); 105 return result_; 106 } 107 108 base::string16 DhcpProxyScriptAdapterFetcher::GetPacScript() const { 109 DCHECK(CalledOnValidThread()); 110 return pac_script_; 111 } 112 113 GURL DhcpProxyScriptAdapterFetcher::GetPacURL() const { 114 DCHECK(CalledOnValidThread()); 115 return pac_url_; 116 } 117 118 DhcpProxyScriptAdapterFetcher::DhcpQuery::DhcpQuery() { 119 } 120 121 DhcpProxyScriptAdapterFetcher::DhcpQuery::~DhcpQuery() { 122 } 123 124 void DhcpProxyScriptAdapterFetcher::DhcpQuery::GetPacURLForAdapter( 125 const std::string& adapter_name) { 126 url_ = ImplGetPacURLFromDhcp(adapter_name); 127 } 128 129 const std::string& DhcpProxyScriptAdapterFetcher::DhcpQuery::url() const { 130 return url_; 131 } 132 133 std::string 134 DhcpProxyScriptAdapterFetcher::DhcpQuery::ImplGetPacURLFromDhcp( 135 const std::string& adapter_name) { 136 return DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(adapter_name); 137 } 138 139 void DhcpProxyScriptAdapterFetcher::OnDhcpQueryDone( 140 scoped_refptr<DhcpQuery> dhcp_query) { 141 DCHECK(CalledOnValidThread()); 142 // Because we can't cancel the call to the Win32 API, we can expect 143 // it to finish while we are in a few different states. The expected 144 // one is WAIT_DHCP, but it could be in CANCEL if Cancel() was called, 145 // or FINISH if timeout occurred. 146 DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_CANCEL || 147 state_ == STATE_FINISH); 148 if (state_ != STATE_WAIT_DHCP) 149 return; 150 151 wait_timer_.Stop(); 152 153 pac_url_ = GURL(dhcp_query->url()); 154 if (pac_url_.is_empty() || !pac_url_.is_valid()) { 155 result_ = ERR_PAC_NOT_IN_DHCP; 156 TransitionToFinish(); 157 } else { 158 state_ = STATE_WAIT_URL; 159 script_fetcher_.reset(ImplCreateScriptFetcher()); 160 script_fetcher_->Fetch( 161 pac_url_, &pac_script_, 162 base::Bind(&DhcpProxyScriptAdapterFetcher::OnFetcherDone, 163 base::Unretained(this))); 164 } 165 } 166 167 void DhcpProxyScriptAdapterFetcher::OnTimeout() { 168 DCHECK_EQ(state_, STATE_WAIT_DHCP); 169 result_ = ERR_TIMED_OUT; 170 TransitionToFinish(); 171 } 172 173 void DhcpProxyScriptAdapterFetcher::OnFetcherDone(int result) { 174 DCHECK(CalledOnValidThread()); 175 DCHECK(state_ == STATE_WAIT_URL || state_ == STATE_CANCEL); 176 if (state_ == STATE_CANCEL) 177 return; 178 179 // At this point, pac_script_ has already been written to. 180 script_fetcher_.reset(); 181 result_ = result; 182 TransitionToFinish(); 183 } 184 185 void DhcpProxyScriptAdapterFetcher::TransitionToFinish() { 186 DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_WAIT_URL); 187 state_ = STATE_FINISH; 188 CompletionCallback callback = callback_; 189 callback_.Reset(); 190 191 // Be careful not to touch any member state after this, as the client 192 // may delete us during this callback. 193 callback.Run(result_); 194 } 195 196 DhcpProxyScriptAdapterFetcher::State 197 DhcpProxyScriptAdapterFetcher::state() const { 198 return state_; 199 } 200 201 ProxyScriptFetcher* DhcpProxyScriptAdapterFetcher::ImplCreateScriptFetcher() { 202 return new ProxyScriptFetcherImpl(url_request_context_); 203 } 204 205 DhcpProxyScriptAdapterFetcher::DhcpQuery* 206 DhcpProxyScriptAdapterFetcher::ImplCreateDhcpQuery() { 207 return new DhcpQuery(); 208 } 209 210 base::TimeDelta DhcpProxyScriptAdapterFetcher::ImplGetTimeout() const { 211 return base::TimeDelta::FromMilliseconds(kTimeoutMs); 212 } 213 214 // static 215 std::string DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp( 216 const std::string& adapter_name) { 217 EnsureDhcpcsvcInit(); 218 219 std::wstring adapter_name_wide = base::SysMultiByteToWide(adapter_name, 220 CP_ACP); 221 222 DHCPCAPI_PARAMS_ARRAY send_params = { 0, NULL }; 223 224 BYTE option_data[] = { 1, 252 }; 225 DHCPCAPI_PARAMS wpad_params = { 0 }; 226 wpad_params.OptionId = 252; 227 wpad_params.IsVendor = FALSE; // Surprising, but intentional. 228 229 DHCPCAPI_PARAMS_ARRAY request_params = { 0 }; 230 request_params.nParams = 1; 231 request_params.Params = &wpad_params; 232 233 // The maximum message size is typically 4096 bytes on Windows per 234 // http://support.microsoft.com/kb/321592 235 DWORD result_buffer_size = 4096; 236 scoped_ptr_malloc<BYTE> result_buffer; 237 int retry_count = 0; 238 DWORD res = NO_ERROR; 239 do { 240 result_buffer.reset(reinterpret_cast<BYTE*>(malloc(result_buffer_size))); 241 242 // Note that while the DHCPCAPI_REQUEST_SYNCHRONOUS flag seems to indicate 243 // there might be an asynchronous mode, there seems to be (at least in 244 // terms of well-documented use of this API) only a synchronous mode, with 245 // an optional "async notifications later if the option changes" mode. 246 // Even IE9, which we hope to emulate as IE is the most widely deployed 247 // previous implementation of the DHCP aspect of WPAD and the only one 248 // on Windows (Konqueror is the other, on Linux), uses this API with the 249 // synchronous flag. There seem to be several Microsoft Knowledge Base 250 // articles about calls to this function failing when other flags are used 251 // (e.g. http://support.microsoft.com/kb/885270) so we won't take any 252 // chances on non-standard, poorly documented usage. 253 res = ::DhcpRequestParams(DHCPCAPI_REQUEST_SYNCHRONOUS, 254 NULL, 255 const_cast<LPWSTR>(adapter_name_wide.c_str()), 256 NULL, 257 send_params, request_params, 258 result_buffer.get(), &result_buffer_size, 259 NULL); 260 ++retry_count; 261 } while (res == ERROR_MORE_DATA && retry_count <= 3); 262 263 if (res != NO_ERROR) { 264 LOG(INFO) << "Error fetching PAC URL from DHCP: " << res; 265 UMA_HISTOGRAM_COUNTS("Net.DhcpWpadUnhandledDhcpError", 1); 266 } else if (wpad_params.nBytesData) { 267 return SanitizeDhcpApiString( 268 reinterpret_cast<const char*>(wpad_params.Data), 269 wpad_params.nBytesData); 270 } 271 272 return ""; 273 } 274 275 // static 276 std::string DhcpProxyScriptAdapterFetcher::SanitizeDhcpApiString( 277 const char* data, size_t count_bytes) { 278 // The result should be ASCII, not wide character. Some DHCP 279 // servers appear to count the trailing NULL in nBytesData, others 280 // do not. A few (we've had one report, http://crbug.com/297810) 281 // do not NULL-terminate but may \n-terminate. 282 // 283 // Belt and suspenders and elastic waistband: First, ensure we 284 // NULL-terminate after nBytesData; this is the inner constructor 285 // with nBytesData as a parameter. Then, return only up to the 286 // first null in case of embedded NULLs; this is the outer 287 // constructor that takes the result of c_str() on the inner. If 288 // the server is giving us back a buffer with embedded NULLs, 289 // something is broken anyway. Finally, trim trailing whitespace. 290 std::string result(std::string(data, count_bytes).c_str()); 291 TrimWhitespaceASCII(result, TRIM_TRAILING, &result); 292 return result; 293 } 294 295 } // namespace net 296