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