OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2011 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 "chrome/browser/password_manager/proxy/chrome_keyring_proxy_client.h" |
| 6 |
| 7 #include <errno.h> |
| 8 #include <sys/socket.h> |
| 9 #include <unistd.h> |
| 10 |
| 11 #include "base/base_paths.h" |
| 12 #include "base/bind.h" |
| 13 #include "base/file_path.h" |
| 14 #include "base/message_loop.h" |
| 15 #include "base/message_pump_libevent.h" |
| 16 #include "base/path_service.h" |
| 17 #include "base/process_util.h" |
| 18 #include "base/string_number_conversions.h" |
| 19 #include "base/stringprintf.h" |
| 20 #include "base/utf_string_conversions.h" |
| 21 #include "chrome/browser/password_manager/proxy/chrome_keyring_proxy.h" |
| 22 #include "chrome/browser/password_manager/proxy/message_reader.h" |
| 23 #include "content/public/browser/browser_thread.h" |
| 24 #include "webkit/glue/password_form.h" |
| 25 |
| 26 // We need a MessageLoopForIO to watch a file descriptor, but the DB thread |
| 27 // doesn't have one. This class handles actually watching and reading from the |
| 28 // file descriptor on the file thread, then notifying the keyring proxy which |
| 29 // parses the messages and notifies the native backend on the DB thread. |
| 30 class KeyringProxyFDWatcher |
| 31 : public base::RefCounted<KeyringProxyFDWatcher>, |
| 32 public base::MessagePumpLibevent::Watcher { |
| 33 public: |
| 34 // Takes ownership of proxy_fd and will close it in the destructor. |
| 35 // Does not take ownership of |client|. ShutDown() must be called before |
| 36 // the client is destroyed to prevent further callbacks; the client |
| 37 // should keep a reference to the new KeyringProxyFDWatcher until then. |
| 38 KeyringProxyFDWatcher(int proxy_fd, ChromeKeyringProxyClient* client); |
| 39 |
| 40 void Init(); |
| 41 |
| 42 int fd() const { return proxy_fd_; } |
| 43 |
| 44 void ShutDown(); |
| 45 |
| 46 private: |
| 47 friend class base::RefCounted<KeyringProxyFDWatcher>; |
| 48 ~KeyringProxyFDWatcher(); |
| 49 |
| 50 // These run on the file thread. |
| 51 void StartWatching(); |
| 52 void StopWatching(); |
| 53 |
| 54 // Implement base::MessagePumpLibevent::Watcher. |
| 55 virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; |
| 56 virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; |
| 57 |
| 58 const int proxy_fd_; |
| 59 ChromeKeyringProxyClient* client_; |
| 60 base::MessagePumpLibevent::FileDescriptorWatcher ipc_watcher_; |
| 61 |
| 62 // Used to batch data read from the proxy into individual replies. |
| 63 MessageReader reader_; |
| 64 |
| 65 DISALLOW_COPY_AND_ASSIGN(KeyringProxyFDWatcher); |
| 66 }; |
| 67 |
| 68 KeyringProxyFDWatcher::KeyringProxyFDWatcher( |
| 69 int proxy_fd, ChromeKeyringProxyClient* client) |
| 70 : proxy_fd_(proxy_fd), client_(client) { |
| 71 // It's not safe to post a task referencing a reference counted object from |
| 72 // within its constructor: the execution of the task races with the |
| 73 // constructor returning and the reference it had may be removed before |
| 74 // another is created. So, we have an Init() method below instead. |
| 75 } |
| 76 |
| 77 void KeyringProxyFDWatcher::Init() { |
| 78 if (client_) { |
| 79 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, |
| 80 base::Bind(&KeyringProxyFDWatcher::StartWatching, |
| 81 this)); |
| 82 } |
| 83 } |
| 84 |
| 85 void KeyringProxyFDWatcher::ShutDown() { |
| 86 // This will prevent any calls back to the client. It is technically |
| 87 // insufficient as this runs on the UI thread and the check occurs on the file |
| 88 // thread, so the file thread may have already checked it before we clear it. |
| 89 // We could fix that with a lock held while client callbacks run on the file |
| 90 // thread, but for now just ignore it - it would only be a problem if you were |
| 91 // actively using the proxy during shutdown, which probably shouldn't happen. |
| 92 client_ = NULL; |
| 93 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, |
| 94 base::Bind(&KeyringProxyFDWatcher::StopWatching, |
| 95 this)); |
| 96 } |
| 97 |
| 98 KeyringProxyFDWatcher::~KeyringProxyFDWatcher() { |
| 99 // It should not be necessary to stop watching here, since we should already |
| 100 // have done that in StopWatching() prior to the last reference going away. |
| 101 // We do it anyway just to be extra sure. |
| 102 ipc_watcher_.StopWatchingFileDescriptor(); |
| 103 close(proxy_fd_); |
| 104 } |
| 105 |
| 106 void KeyringProxyFDWatcher::StartWatching() { |
| 107 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 108 MessageLoopForIO* file_loop = MessageLoopForIO::current(); |
| 109 bool ok = file_loop->WatchFileDescriptor( |
| 110 proxy_fd_, true, MessageLoopForIO::WATCH_READ, &ipc_watcher_, this); |
| 111 DCHECK(ok); |
| 112 } |
| 113 |
| 114 void KeyringProxyFDWatcher::StopWatching() { |
| 115 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 116 ipc_watcher_.StopWatchingFileDescriptor(); |
| 117 } |
| 118 |
| 119 void KeyringProxyFDWatcher::OnFileCanReadWithoutBlocking(int fd) { |
| 120 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 121 DCHECK_EQ(proxy_fd_, fd); |
| 122 // We don't need to read all the available data. If there is still data left |
| 123 // after we return, then we'll get called again because the descriptor will |
| 124 // still be ready to read. So just read a fixed amount, for simplicity. |
| 125 char buffer[256]; |
| 126 ssize_t available = read(proxy_fd_, buffer, sizeof(buffer)); |
| 127 if (available > 0) { |
| 128 // Got some data. Handle it. Note that this may not be a complete message. |
| 129 ssize_t used = 0; |
| 130 while (used < available) { |
| 131 ssize_t handled = reader_.HandleData(&buffer[used], available); |
| 132 DCHECK_GT(handled, 0); |
| 133 used += handled; |
| 134 available -= handled; |
| 135 if (reader_.is_complete()) { |
| 136 if (client_) |
| 137 client_->HandleProxyReply(reader_.lines()); |
| 138 reader_.Reset(); |
| 139 } else { |
| 140 DCHECK_EQ(0, available); |
| 141 } |
| 142 } |
| 143 } else if (available == 0 || errno != EINTR) { |
| 144 // EOF, or an unexpected error. Stop watching and notify. |
| 145 LOG(WARNING) << "Unexpected failure reading from keyring proxy: " << errno; |
| 146 ipc_watcher_.StopWatchingFileDescriptor(); |
| 147 if (client_) |
| 148 client_->HandleProxyError(available == 0); |
| 149 } |
| 150 // Note that if the read is interrupted, we'll just end up here. That's fine. |
| 151 // The file descriptor will still be ready and we'll get called again soon. |
| 152 } |
| 153 |
| 154 void KeyringProxyFDWatcher::OnFileCanWriteWithoutBlocking(int fd) { |
| 155 // We didn't ask to be notified of writability. |
| 156 NOTREACHED(); |
| 157 } |
| 158 |
| 159 const char ChromeKeyringProxyClient::kProxyBinary[] = "chrome-keyring-proxy"; |
| 160 |
| 161 ChromeKeyringProxyClient::ChromeKeyringProxyClient() : next_request_id_(0) { |
| 162 } |
| 163 |
| 164 ChromeKeyringProxyClient::~ChromeKeyringProxyClient() { |
| 165 base::AutoLock lock(request_lock_); |
| 166 if (fd_watcher_.get()) |
| 167 fd_watcher_->ShutDown(); |
| 168 CancelAllRequests(); |
| 169 } |
| 170 |
| 171 bool ChromeKeyringProxyClient::Connect() { |
| 172 base::AutoLock lock(request_lock_); |
| 173 DCHECK(!fd_watcher_.get()); |
| 174 int fd = LaunchKeyringProxy(); |
| 175 if (fd < 0) |
| 176 return false; |
| 177 fd_watcher_ = new KeyringProxyFDWatcher(fd, this); |
| 178 fd_watcher_->Init(); |
| 179 return true; |
| 180 } |
| 181 |
| 182 void ChromeKeyringProxyClient::ConnectForTesting(int fd, bool watch_for_reads) { |
| 183 if (watch_for_reads) { |
| 184 fd_watcher_ = new KeyringProxyFDWatcher(fd, this); |
| 185 fd_watcher_->Init(); |
| 186 } else { |
| 187 fd_watcher_ = new KeyringProxyFDWatcher(fd, NULL); |
| 188 // There's no need to call Init(), as we passed NULL for |client| above. |
| 189 } |
| 190 } |
| 191 |
| 192 void ChromeKeyringProxyClient::AddLogin(const PasswordForm& form, |
| 193 const std::string& app_string, |
| 194 RequestContext* context) { |
| 195 // If we are asked to save a password with 0 date, use the current time. |
| 196 // We don't want to actually save passwords as though on January 1, 1970. |
| 197 time_t date_created = form.date_created.ToTimeT(); |
| 198 if (!date_created) |
| 199 date_created = time(NULL); |
| 200 ProxyMessage request; |
| 201 request.push_back("+" + form.origin.spec()); // Display name. |
| 202 request.push_back("+" + UTF16ToUTF8(form.password_value)); |
| 203 request.push_back("+" + form.origin.spec()); // Origin. |
| 204 request.push_back("+" + form.action.spec()); |
| 205 request.push_back("+" + UTF16ToUTF8(form.username_element)); |
| 206 request.push_back("+" + UTF16ToUTF8(form.username_value)); |
| 207 request.push_back("+" + UTF16ToUTF8(form.password_element)); |
| 208 request.push_back("+" + UTF16ToUTF8(form.submit_element)); |
| 209 request.push_back("+" + form.signon_realm); |
| 210 request.push_back(form.ssl_valid ? "1" : "0"); |
| 211 request.push_back(form.preferred ? "1" : "0"); |
| 212 request.push_back("+" + base::Int64ToString(date_created)); |
| 213 request.push_back(form.blacklisted_by_user ? "1" : "0"); |
| 214 request.push_back(StringPrintf("%d", form.scheme)); |
| 215 request.push_back(app_string); |
| 216 SendRequest(ChromeKeyringProxyCommands::kAddLogin, request, context); |
| 217 } |
| 218 |
| 219 void ChromeKeyringProxyClient::AddLoginSearch(const PasswordForm& form, |
| 220 const std::string& app_string, |
| 221 RequestContext* context) { |
| 222 ProxyMessage request; |
| 223 request.push_back("+" + form.origin.spec()); |
| 224 request.push_back("+" + UTF16ToUTF8(form.username_element)); |
| 225 request.push_back("+" + UTF16ToUTF8(form.username_value)); |
| 226 request.push_back("+" + UTF16ToUTF8(form.password_element)); |
| 227 request.push_back("+" + UTF16ToUTF8(form.submit_element)); |
| 228 request.push_back("+" + form.signon_realm); |
| 229 request.push_back(app_string); |
| 230 SendRequest(ChromeKeyringProxyCommands::kAddLoginSearch, request, context); |
| 231 } |
| 232 |
| 233 void ChromeKeyringProxyClient::UpdateLoginSearch(const PasswordForm& form, |
| 234 const std::string& app_string, |
| 235 RequestContext* context) { |
| 236 ProxyMessage request; |
| 237 request.push_back("+" + form.origin.spec()); |
| 238 request.push_back("+" + UTF16ToUTF8(form.username_element)); |
| 239 request.push_back("+" + UTF16ToUTF8(form.username_value)); |
| 240 request.push_back("+" + UTF16ToUTF8(form.password_element)); |
| 241 request.push_back("+" + form.signon_realm); |
| 242 request.push_back(app_string); |
| 243 SendRequest(ChromeKeyringProxyCommands::kUpdateLoginSearch, request, context); |
| 244 } |
| 245 |
| 246 void ChromeKeyringProxyClient::RemoveLogin(const PasswordForm& form, |
| 247 const std::string& app_string, |
| 248 RequestContext* context) { |
| 249 ProxyMessage request; |
| 250 request.push_back("+" + form.origin.spec()); |
| 251 request.push_back("+" + form.action.spec()); |
| 252 request.push_back("+" + UTF16ToUTF8(form.username_element)); |
| 253 request.push_back("+" + UTF16ToUTF8(form.username_value)); |
| 254 request.push_back("+" + UTF16ToUTF8(form.password_element)); |
| 255 request.push_back("+" + UTF16ToUTF8(form.submit_element)); |
| 256 request.push_back("+" + form.signon_realm); |
| 257 request.push_back(app_string); |
| 258 SendRequest(ChromeKeyringProxyCommands::kRemoveLogin, request, context); |
| 259 } |
| 260 |
| 261 void ChromeKeyringProxyClient::GetLogins(const PasswordForm& form, |
| 262 const std::string& app_string, |
| 263 RequestContext* context) { |
| 264 ProxyMessage request; |
| 265 request.push_back("+" + form.signon_realm); |
| 266 request.push_back(app_string); |
| 267 SendRequest(ChromeKeyringProxyCommands::kGetLogins, request, context); |
| 268 } |
| 269 |
| 270 void ChromeKeyringProxyClient::GetLoginsList(bool blacklisted_by_user, |
| 271 const std::string& app_string, |
| 272 RequestContext* context) { |
| 273 ProxyMessage request; |
| 274 request.push_back(blacklisted_by_user ? "1" : "0"); |
| 275 request.push_back(app_string); |
| 276 SendRequest(ChromeKeyringProxyCommands::kGetLoginsList, request, context); |
| 277 } |
| 278 |
| 279 void ChromeKeyringProxyClient::GetAllLogins(const std::string& app_string, |
| 280 RequestContext* context) { |
| 281 ProxyMessage request; |
| 282 request.push_back(app_string); |
| 283 SendRequest(ChromeKeyringProxyCommands::kGetAllLogins, request, context); |
| 284 } |
| 285 |
| 286 // static |
| 287 int ChromeKeyringProxyClient::LaunchKeyringProxy() { |
| 288 FilePath chrome_dir; |
| 289 if (!PathService::Get(base::DIR_EXE, &chrome_dir)) |
| 290 return -1; |
| 291 std::vector<std::string> proxy_command; |
| 292 proxy_command.push_back(chrome_dir.Append(kProxyBinary).value()); |
| 293 |
| 294 int fds[2]; |
| 295 if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds) < 0) |
| 296 return -1; |
| 297 base::file_handle_mapping_vector ipc; |
| 298 ipc.push_back(std::make_pair(fds[1], STDIN_FILENO)); |
| 299 ipc.push_back(std::make_pair(fds[1], STDOUT_FILENO)); |
| 300 base::LaunchOptions options; |
| 301 options.fds_to_remap = &ipc; |
| 302 |
| 303 base::ProcessHandle proxy_handle; |
| 304 if (!base::LaunchProcess(proxy_command, options, &proxy_handle)) { |
| 305 close(fds[0]); |
| 306 close(fds[1]); |
| 307 return -1; |
| 308 } |
| 309 close(fds[1]); |
| 310 return fds[0]; |
| 311 } |
| 312 |
| 313 int ChromeKeyringProxyClient::GetRequestId() { |
| 314 for (;;) { |
| 315 // |next_request_id_| is actually the most recently used request ID, because |
| 316 // that makes it easier to write this loop with a preincrement operator. |
| 317 if (++next_request_id_ < 0) |
| 318 next_request_id_ = 1; |
| 319 if (proxy_requests_.find(next_request_id_) == proxy_requests_.end()) |
| 320 return next_request_id_; |
| 321 } |
| 322 } |
| 323 |
| 324 void ChromeKeyringProxyClient::CancelRequest(RequestContext* context) { |
| 325 context->result_code = GNOME_KEYRING_RESULT_CANCELLED; |
| 326 context->event.Signal(); |
| 327 } |
| 328 |
| 329 void ChromeKeyringProxyClient::CancelAllRequests() { |
| 330 for (std::map<int, RequestContext*>::iterator it = proxy_requests_.begin(); |
| 331 it != proxy_requests_.end(); ++it) { |
| 332 CancelRequest(it->second); |
| 333 } |
| 334 proxy_requests_.clear(); |
| 335 } |
| 336 |
| 337 void ChromeKeyringProxyClient::SendRequest(char type, |
| 338 const ProxyMessage& request, |
| 339 RequestContext* context) { |
| 340 base::AutoLock lock(request_lock_); |
| 341 if (!fd_watcher_.get()) { |
| 342 LOG(WARNING) << "Attempted to use unconnected keyring proxy"; |
| 343 CancelRequest(context); |
| 344 return; |
| 345 } |
| 346 int id = GetRequestId(); |
| 347 proxy_requests_[id] = context; |
| 348 std::string message = StringPrintf("%c%d\n", type, id); |
| 349 for (size_t i = 0; i < request.size(); ++i) |
| 350 message += request[i] + "\n"; |
| 351 message += "\n"; |
| 352 size_t written = write(fd_watcher_->fd(), message.data(), message.size()); |
| 353 if (written != message.size()) { |
| 354 LOG(ERROR) << "Failed to write to keyring proxy! Likely failure soon..."; |
| 355 CancelRequest(context); |
| 356 } |
| 357 } |
| 358 |
| 359 void ChromeKeyringProxyClient::HandleProxyReply(const ProxyMessage& reply) { |
| 360 if (!reply.size()) { |
| 361 LOG(WARNING) << "Got empty reply from keyring proxy"; |
| 362 return; |
| 363 } |
| 364 int id, result_code; |
| 365 if (sscanf(reply[0].c_str(), "%d %d", &id, &result_code) != 2) { |
| 366 // We haven't acquired the lock yet so we can just use HandleProxyError(). |
| 367 HandleProxyError(false); |
| 368 return; |
| 369 } |
| 370 base::AutoLock lock(request_lock_); |
| 371 std::map<int, RequestContext*>::iterator it = proxy_requests_.find(id); |
| 372 if (it == proxy_requests_.end()) { |
| 373 LOG(WARNING) << "Unexpected reply from keyring proxy (ID " << id << ")"; |
| 374 return; |
| 375 } |
| 376 RequestContext* context = it->second; |
| 377 const size_t kLinesPerPass = 13; |
| 378 if ((reply.size() - 1) % kLinesPerPass != 0 || |
| 379 (reply.size() > 1 && !context->result_list)) { |
| 380 LOG(WARNING) << "Invalid reply from keyring proxy (ID " << id << ")"; |
| 381 fd_watcher_->ShutDown(); |
| 382 fd_watcher_ = NULL; |
| 383 CancelAllRequests(); |
| 384 return; |
| 385 } |
| 386 // We don't remove the request from the map until after the checks above. |
| 387 proxy_requests_.erase(it); |
| 388 context->result_code = static_cast<GnomeKeyringResult>(result_code); |
| 389 for (size_t i = 1; i + kLinesPerPass <= reply.size(); i += kLinesPerPass) { |
| 390 PasswordForm* form = new PasswordForm; |
| 391 form->origin = GURL(reply[i].substr(1)); |
| 392 form->action = GURL(reply[i + 1].substr(1)); |
| 393 form->username_element = UTF8ToUTF16(reply[i + 2].substr(1)); |
| 394 form->username_value = UTF8ToUTF16(reply[i + 3].substr(1)); |
| 395 form->password_element = UTF8ToUTF16(reply[i + 4].substr(1)); |
| 396 form->password_value = UTF8ToUTF16(reply[i + 5].substr(1)); |
| 397 form->submit_element = UTF8ToUTF16(reply[i + 6].substr(1)); |
| 398 form->signon_realm = reply[i + 7].substr(1); |
| 399 form->ssl_valid = reply[i + 8] != "0"; |
| 400 form->preferred = reply[i + 9] != "0"; |
| 401 int64 date_created = 0; |
| 402 bool date_ok = base::StringToInt64(reply[i + 10].substr(1), &date_created); |
| 403 DCHECK(date_ok); |
| 404 form->date_created = base::Time::FromTimeT(date_created); |
| 405 form->blacklisted_by_user = reply[i + 11] != "0"; |
| 406 unsigned int scheme = 0; |
| 407 int scheme_ok = sscanf(reply[i + 12].c_str(), "%u", &scheme); |
| 408 DCHECK_EQ(1, scheme_ok); |
| 409 form->scheme = static_cast<PasswordForm::Scheme>(scheme); |
| 410 context->result_list->push_back(form); |
| 411 } |
| 412 context->event.Signal(); |
| 413 } |
| 414 |
| 415 void ChromeKeyringProxyClient::HandleProxyError(bool eof) { |
| 416 base::AutoLock lock(request_lock_); |
| 417 if (eof) |
| 418 LOG(WARNING) << "Unexpected EOF reading from keyring proxy"; |
| 419 else |
| 420 LOG(WARNING) << "Invalid reply from keyring proxy"; |
| 421 fd_watcher_->ShutDown(); |
| 422 fd_watcher_ = NULL; |
| 423 CancelAllRequests(); |
| 424 } |
OLD | NEW |