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 "chrome/browser/password_manager/native_backend_kwallet_x.h" 6 7 #include <vector> 8 9 #include "base/bind.h" 10 #include "base/logging.h" 11 #include "base/pickle.h" 12 #include "base/stl_util.h" 13 #include "base/strings/stringprintf.h" 14 #include "base/synchronization/waitable_event.h" 15 #include "base/threading/thread_restrictions.h" 16 #include "chrome/grit/chromium_strings.h" 17 #include "components/autofill/core/common/password_form.h" 18 #include "content/public/browser/browser_thread.h" 19 #include "dbus/bus.h" 20 #include "dbus/message.h" 21 #include "dbus/object_path.h" 22 #include "dbus/object_proxy.h" 23 #include "ui/base/l10n/l10n_util.h" 24 25 using autofill::PasswordForm; 26 using content::BrowserThread; 27 28 namespace { 29 30 // We could localize this string, but then changing your locale would cause 31 // you to lose access to all your stored passwords. Maybe best not to do that. 32 // Name of the folder to store passwords in. 33 const char kKWalletFolder[] = "Chrome Form Data"; 34 35 // DBus service, path, and interface names for klauncher and kwalletd. 36 const char kKWalletServiceName[] = "org.kde.kwalletd"; 37 const char kKWalletPath[] = "/modules/kwalletd"; 38 const char kKWalletInterface[] = "org.kde.KWallet"; 39 const char kKLauncherServiceName[] = "org.kde.klauncher"; 40 const char kKLauncherPath[] = "/KLauncher"; 41 const char kKLauncherInterface[] = "org.kde.KLauncher"; 42 43 // Compares two PasswordForms and returns true if they are the same. 44 // If |update_check| is false, we only check the fields that are checked by 45 // LoginDatabase::UpdateLogin() when updating logins; otherwise, we check the 46 // fields that are checked by LoginDatabase::RemoveLogin() for removing them. 47 bool CompareForms(const autofill::PasswordForm& a, 48 const autofill::PasswordForm& b, 49 bool update_check) { 50 // An update check doesn't care about the submit element. 51 if (!update_check && a.submit_element != b.submit_element) 52 return false; 53 return a.origin == b.origin && 54 a.password_element == b.password_element && 55 a.signon_realm == b.signon_realm && 56 a.username_element == b.username_element && 57 a.username_value == b.username_value; 58 } 59 60 // Checks a serialized list of PasswordForms for sanity. Returns true if OK. 61 // Note that |realm| is only used for generating a useful warning message. 62 bool CheckSerializedValue(const uint8_t* byte_array, 63 size_t length, 64 const std::string& realm) { 65 const Pickle::Header* header = 66 reinterpret_cast<const Pickle::Header*>(byte_array); 67 if (length < sizeof(*header) || 68 header->payload_size > length - sizeof(*header)) { 69 LOG(WARNING) << "Invalid KWallet entry detected (realm: " << realm << ")"; 70 return false; 71 } 72 return true; 73 } 74 75 // Convenience function to read a GURL from a Pickle. Assumes the URL has 76 // been written as a UTF-8 string. Returns true on success. 77 bool ReadGURL(PickleIterator* iter, bool warn_only, GURL* url) { 78 std::string url_string; 79 if (!iter->ReadString(&url_string)) { 80 if (!warn_only) 81 LOG(ERROR) << "Failed to deserialize URL."; 82 *url = GURL(); 83 return false; 84 } 85 *url = GURL(url_string); 86 return true; 87 } 88 89 void LogDeserializationWarning(int version, 90 std::string signon_realm, 91 bool warn_only) { 92 if (warn_only) { 93 LOG(WARNING) << "Failed to deserialize version " << version 94 << " KWallet entry (realm: " << signon_realm 95 << ") with native architecture size; will try alternate " 96 << "size."; 97 } else { 98 LOG(ERROR) << "Failed to deserialize version " << version 99 << " KWallet entry (realm: " << signon_realm << ")"; 100 } 101 } 102 103 } // namespace 104 105 NativeBackendKWallet::NativeBackendKWallet(LocalProfileId id) 106 : profile_id_(id), 107 kwallet_proxy_(NULL), 108 app_name_(l10n_util::GetStringUTF8(IDS_PRODUCT_NAME)) { 109 folder_name_ = GetProfileSpecificFolderName(); 110 } 111 112 NativeBackendKWallet::~NativeBackendKWallet() { 113 // This destructor is called on the thread that is destroying the Profile 114 // containing the PasswordStore that owns this NativeBackend. Generally that 115 // won't be the DB thread; it will be the UI thread. So we post a message to 116 // shut it down on the DB thread, and it will be destructed afterward when the 117 // scoped_refptr<dbus::Bus> goes out of scope. The NativeBackend will be 118 // destroyed before that occurs, but that's OK. 119 if (session_bus_.get()) { 120 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 121 base::Bind(&dbus::Bus::ShutdownAndBlock, 122 session_bus_.get())); 123 } 124 } 125 126 bool NativeBackendKWallet::Init() { 127 // Without the |optional_bus| parameter, a real bus will be instantiated. 128 return InitWithBus(scoped_refptr<dbus::Bus>()); 129 } 130 131 bool NativeBackendKWallet::InitWithBus(scoped_refptr<dbus::Bus> optional_bus) { 132 // We must synchronously do a few DBus calls to figure out if initialization 133 // succeeds, but later, we'll want to do most work on the DB thread. So we 134 // have to do the initialization on the DB thread here too, and wait for it. 135 bool success = false; 136 base::WaitableEvent event(false, false); 137 // NativeBackendKWallet isn't reference counted, but we wait for InitWithBus 138 // to finish, so we can safely use base::Unretained here. 139 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, 140 base::Bind(&NativeBackendKWallet::InitOnDBThread, 141 base::Unretained(this), 142 optional_bus, &event, &success)); 143 144 // This ScopedAllowWait should not be here. http://crbug.com/125331 145 base::ThreadRestrictions::ScopedAllowWait allow_wait; 146 event.Wait(); 147 return success; 148 } 149 150 void NativeBackendKWallet::InitOnDBThread(scoped_refptr<dbus::Bus> optional_bus, 151 base::WaitableEvent* event, 152 bool* success) { 153 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 154 DCHECK(!session_bus_.get()); 155 if (optional_bus.get()) { 156 // The optional_bus parameter is given when this method is called in tests. 157 session_bus_ = optional_bus; 158 } else { 159 // Get a (real) connection to the session bus. 160 dbus::Bus::Options options; 161 options.bus_type = dbus::Bus::SESSION; 162 options.connection_type = dbus::Bus::PRIVATE; 163 session_bus_ = new dbus::Bus(options); 164 } 165 kwallet_proxy_ = 166 session_bus_->GetObjectProxy(kKWalletServiceName, 167 dbus::ObjectPath(kKWalletPath)); 168 // kwalletd may not be running. If we get a temporary failure initializing it, 169 // try to start it and then try again. (Note the short-circuit evaluation.) 170 const InitResult result = InitWallet(); 171 *success = (result == INIT_SUCCESS || 172 (result == TEMPORARY_FAIL && 173 StartKWalletd() && InitWallet() == INIT_SUCCESS)); 174 event->Signal(); 175 } 176 177 bool NativeBackendKWallet::StartKWalletd() { 178 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 179 // Sadly kwalletd doesn't use DBus activation, so we have to make a call to 180 // klauncher to start it. 181 dbus::ObjectProxy* klauncher = 182 session_bus_->GetObjectProxy(kKLauncherServiceName, 183 dbus::ObjectPath(kKLauncherPath)); 184 185 dbus::MethodCall method_call(kKLauncherInterface, 186 "start_service_by_desktop_name"); 187 dbus::MessageWriter builder(&method_call); 188 std::vector<std::string> empty; 189 builder.AppendString("kwalletd"); // serviceName 190 builder.AppendArrayOfStrings(empty); // urls 191 builder.AppendArrayOfStrings(empty); // envs 192 builder.AppendString(std::string()); // startup_id 193 builder.AppendBool(false); // blind 194 scoped_ptr<dbus::Response> response( 195 klauncher->CallMethodAndBlock( 196 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); 197 if (!response.get()) { 198 LOG(ERROR) << "Error contacting klauncher to start kwalletd"; 199 return false; 200 } 201 dbus::MessageReader reader(response.get()); 202 int32_t ret = -1; 203 std::string dbus_name; 204 std::string error; 205 int32_t pid = -1; 206 if (!reader.PopInt32(&ret) || !reader.PopString(&dbus_name) || 207 !reader.PopString(&error) || !reader.PopInt32(&pid)) { 208 LOG(ERROR) << "Error reading response from klauncher to start kwalletd: " 209 << response->ToString(); 210 return false; 211 } 212 if (!error.empty() || ret) { 213 LOG(ERROR) << "Error launching kwalletd: error '" << error << "' " 214 << " (code " << ret << ")"; 215 return false; 216 } 217 218 return true; 219 } 220 221 NativeBackendKWallet::InitResult NativeBackendKWallet::InitWallet() { 222 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 223 { 224 // Check that KWallet is enabled. 225 dbus::MethodCall method_call(kKWalletInterface, "isEnabled"); 226 scoped_ptr<dbus::Response> response( 227 kwallet_proxy_->CallMethodAndBlock( 228 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); 229 if (!response.get()) { 230 LOG(ERROR) << "Error contacting kwalletd (isEnabled)"; 231 return TEMPORARY_FAIL; 232 } 233 dbus::MessageReader reader(response.get()); 234 bool enabled = false; 235 if (!reader.PopBool(&enabled)) { 236 LOG(ERROR) << "Error reading response from kwalletd (isEnabled): " 237 << response->ToString(); 238 return PERMANENT_FAIL; 239 } 240 // Not enabled? Don't use KWallet. But also don't warn here. 241 if (!enabled) { 242 VLOG(1) << "kwalletd reports that KWallet is not enabled."; 243 return PERMANENT_FAIL; 244 } 245 } 246 247 { 248 // Get the wallet name. 249 dbus::MethodCall method_call(kKWalletInterface, "networkWallet"); 250 scoped_ptr<dbus::Response> response( 251 kwallet_proxy_->CallMethodAndBlock( 252 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); 253 if (!response.get()) { 254 LOG(ERROR) << "Error contacting kwalletd (networkWallet)"; 255 return TEMPORARY_FAIL; 256 } 257 dbus::MessageReader reader(response.get()); 258 if (!reader.PopString(&wallet_name_)) { 259 LOG(ERROR) << "Error reading response from kwalletd (networkWallet): " 260 << response->ToString(); 261 return PERMANENT_FAIL; 262 } 263 } 264 265 return INIT_SUCCESS; 266 } 267 268 password_manager::PasswordStoreChangeList NativeBackendKWallet::AddLogin( 269 const PasswordForm& form) { 270 int wallet_handle = WalletHandle(); 271 if (wallet_handle == kInvalidKWalletHandle) 272 return password_manager::PasswordStoreChangeList(); 273 274 ScopedVector<autofill::PasswordForm> forms; 275 GetLoginsList(&forms.get(), form.signon_realm, wallet_handle); 276 277 // We search for a login to update, rather than unconditionally appending the 278 // login, because in some cases (especially involving sync) we can be asked to 279 // add a login that already exists. In these cases we want to just update. 280 bool updated = false; 281 password_manager::PasswordStoreChangeList changes; 282 for (size_t i = 0; i < forms.size(); ++i) { 283 // Use the more restrictive removal comparison, so that we never have 284 // duplicate logins that would all be removed together by RemoveLogin(). 285 if (CompareForms(form, *forms[i], false)) { 286 changes.push_back(password_manager::PasswordStoreChange( 287 password_manager::PasswordStoreChange::REMOVE, *forms[i])); 288 *forms[i] = form; 289 updated = true; 290 } 291 } 292 if (!updated) 293 forms.push_back(new PasswordForm(form)); 294 changes.push_back(password_manager::PasswordStoreChange( 295 password_manager::PasswordStoreChange::ADD, form)); 296 297 bool ok = SetLoginsList(forms.get(), form.signon_realm, wallet_handle); 298 if (!ok) 299 changes.clear(); 300 301 return changes; 302 } 303 304 bool NativeBackendKWallet::UpdateLogin( 305 const PasswordForm& form, 306 password_manager::PasswordStoreChangeList* changes) { 307 DCHECK(changes); 308 changes->clear(); 309 int wallet_handle = WalletHandle(); 310 if (wallet_handle == kInvalidKWalletHandle) 311 return false; 312 313 ScopedVector<autofill::PasswordForm> forms; 314 GetLoginsList(&forms.get(), form.signon_realm, wallet_handle); 315 316 bool updated = false; 317 for (size_t i = 0; i < forms.size(); ++i) { 318 if (CompareForms(form, *forms[i], true)) { 319 *forms[i] = form; 320 updated = true; 321 } 322 } 323 if (!updated) 324 return true; 325 326 if (SetLoginsList(forms.get(), form.signon_realm, wallet_handle)) { 327 changes->push_back(password_manager::PasswordStoreChange( 328 password_manager::PasswordStoreChange::UPDATE, form)); 329 return true; 330 } 331 332 return false; 333 } 334 335 bool NativeBackendKWallet::RemoveLogin(const PasswordForm& form) { 336 int wallet_handle = WalletHandle(); 337 if (wallet_handle == kInvalidKWalletHandle) 338 return false; 339 340 PasswordFormList all_forms; 341 GetLoginsList(&all_forms, form.signon_realm, wallet_handle); 342 343 PasswordFormList kept_forms; 344 kept_forms.reserve(all_forms.size()); 345 for (size_t i = 0; i < all_forms.size(); ++i) { 346 if (CompareForms(form, *all_forms[i], false)) 347 delete all_forms[i]; 348 else 349 kept_forms.push_back(all_forms[i]); 350 } 351 352 // Update the entry in the wallet, possibly deleting it. 353 bool ok = SetLoginsList(kept_forms, form.signon_realm, wallet_handle); 354 355 STLDeleteElements(&kept_forms); 356 return ok; 357 } 358 359 bool NativeBackendKWallet::RemoveLoginsCreatedBetween( 360 base::Time delete_begin, 361 base::Time delete_end, 362 password_manager::PasswordStoreChangeList* changes) { 363 return RemoveLoginsBetween( 364 delete_begin, delete_end, CREATION_TIMESTAMP, changes); 365 } 366 367 bool NativeBackendKWallet::RemoveLoginsSyncedBetween( 368 base::Time delete_begin, 369 base::Time delete_end, 370 password_manager::PasswordStoreChangeList* changes) { 371 return RemoveLoginsBetween(delete_begin, delete_end, SYNC_TIMESTAMP, changes); 372 } 373 374 bool NativeBackendKWallet::GetLogins(const PasswordForm& form, 375 PasswordFormList* forms) { 376 int wallet_handle = WalletHandle(); 377 if (wallet_handle == kInvalidKWalletHandle) 378 return false; 379 return GetLoginsList(forms, form.signon_realm, wallet_handle); 380 } 381 382 bool NativeBackendKWallet::GetAutofillableLogins(PasswordFormList* forms) { 383 int wallet_handle = WalletHandle(); 384 if (wallet_handle == kInvalidKWalletHandle) 385 return false; 386 return GetLoginsList(forms, true, wallet_handle); 387 } 388 389 bool NativeBackendKWallet::GetBlacklistLogins(PasswordFormList* forms) { 390 int wallet_handle = WalletHandle(); 391 if (wallet_handle == kInvalidKWalletHandle) 392 return false; 393 return GetLoginsList(forms, false, wallet_handle); 394 } 395 396 bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms, 397 const std::string& signon_realm, 398 int wallet_handle) { 399 // Is there an entry in the wallet? 400 { 401 dbus::MethodCall method_call(kKWalletInterface, "hasEntry"); 402 dbus::MessageWriter builder(&method_call); 403 builder.AppendInt32(wallet_handle); // handle 404 builder.AppendString(folder_name_); // folder 405 builder.AppendString(signon_realm); // key 406 builder.AppendString(app_name_); // appid 407 scoped_ptr<dbus::Response> response( 408 kwallet_proxy_->CallMethodAndBlock( 409 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); 410 if (!response.get()) { 411 LOG(ERROR) << "Error contacting kwalletd (hasEntry)"; 412 return false; 413 } 414 dbus::MessageReader reader(response.get()); 415 bool has_entry = false; 416 if (!reader.PopBool(&has_entry)) { 417 LOG(ERROR) << "Error reading response from kwalletd (hasEntry): " 418 << response->ToString(); 419 return false; 420 } 421 if (!has_entry) { 422 // This is not an error. There just isn't a matching entry. 423 return true; 424 } 425 } 426 427 { 428 dbus::MethodCall method_call(kKWalletInterface, "readEntry"); 429 dbus::MessageWriter builder(&method_call); 430 builder.AppendInt32(wallet_handle); // handle 431 builder.AppendString(folder_name_); // folder 432 builder.AppendString(signon_realm); // key 433 builder.AppendString(app_name_); // appid 434 scoped_ptr<dbus::Response> response( 435 kwallet_proxy_->CallMethodAndBlock( 436 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); 437 if (!response.get()) { 438 LOG(ERROR) << "Error contacting kwalletd (readEntry)"; 439 return false; 440 } 441 dbus::MessageReader reader(response.get()); 442 const uint8_t* bytes = NULL; 443 size_t length = 0; 444 if (!reader.PopArrayOfBytes(&bytes, &length)) { 445 LOG(ERROR) << "Error reading response from kwalletd (readEntry): " 446 << response->ToString(); 447 return false; 448 } 449 if (!bytes) 450 return false; 451 if (!CheckSerializedValue(bytes, length, signon_realm)) { 452 // This is weird, but we choose not to call it an error. There is an 453 // invalid entry somehow, but by just ignoring it, we make it easier to 454 // repair without having to delete it using kwalletmanager (that is, by 455 // just saving a new password within this realm to overwrite it). 456 return true; 457 } 458 459 // Can't we all just agree on whether bytes are signed or not? Please? 460 Pickle pickle(reinterpret_cast<const char*>(bytes), length); 461 PasswordFormList all_forms; 462 DeserializeValue(signon_realm, pickle, forms); 463 } 464 465 return true; 466 } 467 468 bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms, 469 bool autofillable, 470 int wallet_handle) { 471 PasswordFormList all_forms; 472 if (!GetAllLogins(&all_forms, wallet_handle)) 473 return false; 474 475 // We have to read all the entries, and then filter them here. 476 forms->reserve(forms->size() + all_forms.size()); 477 for (size_t i = 0; i < all_forms.size(); ++i) { 478 if (all_forms[i]->blacklisted_by_user == !autofillable) 479 forms->push_back(all_forms[i]); 480 else 481 delete all_forms[i]; 482 } 483 484 return true; 485 } 486 487 bool NativeBackendKWallet::GetAllLogins(PasswordFormList* forms, 488 int wallet_handle) { 489 // We could probably also use readEntryList here. 490 std::vector<std::string> realm_list; 491 { 492 dbus::MethodCall method_call(kKWalletInterface, "entryList"); 493 dbus::MessageWriter builder(&method_call); 494 builder.AppendInt32(wallet_handle); // handle 495 builder.AppendString(folder_name_); // folder 496 builder.AppendString(app_name_); // appid 497 scoped_ptr<dbus::Response> response( 498 kwallet_proxy_->CallMethodAndBlock( 499 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); 500 if (!response.get()) { 501 LOG(ERROR) << "Error contacting kwalletd (entryList)"; 502 return false; 503 } 504 dbus::MessageReader reader(response.get()); 505 if (!reader.PopArrayOfStrings(&realm_list)) { 506 LOG(ERROR) << "Error reading response from kwalletd (entryList): " 507 << response->ToString(); 508 return false; 509 } 510 } 511 512 for (size_t i = 0; i < realm_list.size(); ++i) { 513 const std::string& signon_realm = realm_list[i]; 514 dbus::MethodCall method_call(kKWalletInterface, "readEntry"); 515 dbus::MessageWriter builder(&method_call); 516 builder.AppendInt32(wallet_handle); // handle 517 builder.AppendString(folder_name_); // folder 518 builder.AppendString(signon_realm); // key 519 builder.AppendString(app_name_); // appid 520 scoped_ptr<dbus::Response> response( 521 kwallet_proxy_->CallMethodAndBlock( 522 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); 523 if (!response.get()) { 524 LOG(ERROR) << "Error contacting kwalletd (readEntry)"; 525 continue; 526 } 527 dbus::MessageReader reader(response.get()); 528 const uint8_t* bytes = NULL; 529 size_t length = 0; 530 if (!reader.PopArrayOfBytes(&bytes, &length)) { 531 LOG(ERROR) << "Error reading response from kwalletd (readEntry): " 532 << response->ToString(); 533 continue; 534 } 535 if (!bytes || !CheckSerializedValue(bytes, length, signon_realm)) 536 continue; 537 538 // Can't we all just agree on whether bytes are signed or not? Please? 539 Pickle pickle(reinterpret_cast<const char*>(bytes), length); 540 PasswordFormList all_forms; 541 DeserializeValue(signon_realm, pickle, forms); 542 } 543 return true; 544 } 545 546 bool NativeBackendKWallet::SetLoginsList(const PasswordFormList& forms, 547 const std::string& signon_realm, 548 int wallet_handle) { 549 if (forms.empty()) { 550 // No items left? Remove the entry from the wallet. 551 dbus::MethodCall method_call(kKWalletInterface, "removeEntry"); 552 dbus::MessageWriter builder(&method_call); 553 builder.AppendInt32(wallet_handle); // handle 554 builder.AppendString(folder_name_); // folder 555 builder.AppendString(signon_realm); // key 556 builder.AppendString(app_name_); // appid 557 scoped_ptr<dbus::Response> response( 558 kwallet_proxy_->CallMethodAndBlock( 559 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); 560 if (!response.get()) { 561 LOG(ERROR) << "Error contacting kwalletd (removeEntry)"; 562 return kInvalidKWalletHandle; 563 } 564 dbus::MessageReader reader(response.get()); 565 int ret = 0; 566 if (!reader.PopInt32(&ret)) { 567 LOG(ERROR) << "Error reading response from kwalletd (removeEntry): " 568 << response->ToString(); 569 return false; 570 } 571 if (ret != 0) 572 LOG(ERROR) << "Bad return code " << ret << " from KWallet removeEntry"; 573 return ret == 0; 574 } 575 576 Pickle value; 577 SerializeValue(forms, &value); 578 579 dbus::MethodCall method_call(kKWalletInterface, "writeEntry"); 580 dbus::MessageWriter builder(&method_call); 581 builder.AppendInt32(wallet_handle); // handle 582 builder.AppendString(folder_name_); // folder 583 builder.AppendString(signon_realm); // key 584 builder.AppendArrayOfBytes(static_cast<const uint8_t*>(value.data()), 585 value.size()); // value 586 builder.AppendString(app_name_); // appid 587 scoped_ptr<dbus::Response> response( 588 kwallet_proxy_->CallMethodAndBlock( 589 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); 590 if (!response.get()) { 591 LOG(ERROR) << "Error contacting kwalletd (writeEntry)"; 592 return kInvalidKWalletHandle; 593 } 594 dbus::MessageReader reader(response.get()); 595 int ret = 0; 596 if (!reader.PopInt32(&ret)) { 597 LOG(ERROR) << "Error reading response from kwalletd (writeEntry): " 598 << response->ToString(); 599 return false; 600 } 601 if (ret != 0) 602 LOG(ERROR) << "Bad return code " << ret << " from KWallet writeEntry"; 603 return ret == 0; 604 } 605 606 bool NativeBackendKWallet::RemoveLoginsBetween( 607 base::Time delete_begin, 608 base::Time delete_end, 609 TimestampToCompare date_to_compare, 610 password_manager::PasswordStoreChangeList* changes) { 611 DCHECK(changes); 612 changes->clear(); 613 int wallet_handle = WalletHandle(); 614 if (wallet_handle == kInvalidKWalletHandle) 615 return false; 616 617 // We could probably also use readEntryList here. 618 std::vector<std::string> realm_list; 619 { 620 dbus::MethodCall method_call(kKWalletInterface, "entryList"); 621 dbus::MessageWriter builder(&method_call); 622 builder.AppendInt32(wallet_handle); // handle 623 builder.AppendString(folder_name_); // folder 624 builder.AppendString(app_name_); // appid 625 scoped_ptr<dbus::Response> response(kwallet_proxy_->CallMethodAndBlock( 626 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); 627 if (!response.get()) { 628 LOG(ERROR) << "Error contacting kwalletd (entryList)"; 629 return false; 630 } 631 dbus::MessageReader reader(response.get()); 632 dbus::MessageReader array(response.get()); 633 if (!reader.PopArray(&array)) { 634 LOG(ERROR) << "Error reading response from kwalletd (entryList): " 635 << response->ToString(); 636 return false; 637 } 638 while (array.HasMoreData()) { 639 std::string realm; 640 if (!array.PopString(&realm)) { 641 LOG(ERROR) << "Error reading response from kwalletd (entryList): " 642 << response->ToString(); 643 return false; 644 } 645 realm_list.push_back(realm); 646 } 647 } 648 649 bool ok = true; 650 for (size_t i = 0; i < realm_list.size(); ++i) { 651 const std::string& signon_realm = realm_list[i]; 652 dbus::MethodCall method_call(kKWalletInterface, "readEntry"); 653 dbus::MessageWriter builder(&method_call); 654 builder.AppendInt32(wallet_handle); // handle 655 builder.AppendString(folder_name_); // folder 656 builder.AppendString(signon_realm); // key 657 builder.AppendString(app_name_); // appid 658 scoped_ptr<dbus::Response> response(kwallet_proxy_->CallMethodAndBlock( 659 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); 660 if (!response.get()) { 661 LOG(ERROR) << "Error contacting kwalletd (readEntry)"; 662 continue; 663 } 664 dbus::MessageReader reader(response.get()); 665 const uint8_t* bytes = NULL; 666 size_t length = 0; 667 if (!reader.PopArrayOfBytes(&bytes, &length)) { 668 LOG(ERROR) << "Error reading response from kwalletd (readEntry): " 669 << response->ToString(); 670 continue; 671 } 672 if (!bytes || !CheckSerializedValue(bytes, length, signon_realm)) 673 continue; 674 675 // Can't we all just agree on whether bytes are signed or not? Please? 676 Pickle pickle(reinterpret_cast<const char*>(bytes), length); 677 PasswordFormList all_forms; 678 DeserializeValue(signon_realm, pickle, &all_forms); 679 680 PasswordFormList kept_forms; 681 kept_forms.reserve(all_forms.size()); 682 base::Time autofill::PasswordForm::*date_member = 683 date_to_compare == CREATION_TIMESTAMP 684 ? &autofill::PasswordForm::date_created 685 : &autofill::PasswordForm::date_synced; 686 for (size_t i = 0; i < all_forms.size(); ++i) { 687 if (delete_begin <= all_forms[i]->*date_member && 688 (delete_end.is_null() || all_forms[i]->*date_member < delete_end)) { 689 changes->push_back(password_manager::PasswordStoreChange( 690 password_manager::PasswordStoreChange::REMOVE, *all_forms[i])); 691 delete all_forms[i]; 692 } else { 693 kept_forms.push_back(all_forms[i]); 694 } 695 } 696 697 if (!SetLoginsList(kept_forms, signon_realm, wallet_handle)) { 698 ok = false; 699 changes->clear(); 700 } 701 STLDeleteElements(&kept_forms); 702 } 703 return ok; 704 } 705 706 // static 707 void NativeBackendKWallet::SerializeValue(const PasswordFormList& forms, 708 Pickle* pickle) { 709 pickle->WriteInt(kPickleVersion); 710 pickle->WriteUInt64(forms.size()); 711 for (PasswordFormList::const_iterator it = forms.begin(); 712 it != forms.end(); ++it) { 713 const PasswordForm* form = *it; 714 pickle->WriteInt(form->scheme); 715 pickle->WriteString(form->origin.spec()); 716 pickle->WriteString(form->action.spec()); 717 pickle->WriteString16(form->username_element); 718 pickle->WriteString16(form->username_value); 719 pickle->WriteString16(form->password_element); 720 pickle->WriteString16(form->password_value); 721 pickle->WriteString16(form->submit_element); 722 pickle->WriteBool(form->ssl_valid); 723 pickle->WriteBool(form->preferred); 724 pickle->WriteBool(form->blacklisted_by_user); 725 pickle->WriteInt64(form->date_created.ToTimeT()); 726 pickle->WriteInt(form->type); 727 pickle->WriteInt(form->times_used); 728 autofill::SerializeFormData(form->form_data, pickle); 729 pickle->WriteInt64(form->date_synced.ToInternalValue()); 730 pickle->WriteString16(form->display_name); 731 pickle->WriteString(form->avatar_url.spec()); 732 pickle->WriteString(form->federation_url.spec()); 733 pickle->WriteBool(form->is_zero_click); 734 } 735 } 736 737 // static 738 bool NativeBackendKWallet::DeserializeValueSize(const std::string& signon_realm, 739 const PickleIterator& init_iter, 740 int version, 741 bool size_32, 742 bool warn_only, 743 PasswordFormList* forms) { 744 PickleIterator iter = init_iter; 745 746 uint64_t count = 0; 747 if (size_32) { 748 uint32_t count_32 = 0; 749 if (!iter.ReadUInt32(&count_32)) { 750 LOG(ERROR) << "Failed to deserialize KWallet entry " 751 << "(realm: " << signon_realm << ")"; 752 return false; 753 } 754 count = count_32; 755 } else { 756 if (!iter.ReadUInt64(&count)) { 757 LOG(ERROR) << "Failed to deserialize KWallet entry " 758 << "(realm: " << signon_realm << ")"; 759 return false; 760 } 761 } 762 763 if (count > 0xFFFF) { 764 // Trying to pin down the cause of http://crbug.com/80728 (or fix it). 765 // This is a very large number of passwords to be saved for a single realm. 766 // It is almost certainly a corrupt pickle and not real data. Ignore it. 767 // This very well might actually be http://crbug.com/107701, so if we're 768 // reading an old pickle, we don't even log this the first time we try to 769 // read it. (That is, when we're reading the native architecture size.) 770 if (!warn_only) { 771 LOG(ERROR) << "Suspiciously large number of entries in KWallet entry " 772 << "(" << count << "; realm: " << signon_realm << ")"; 773 } 774 return false; 775 } 776 777 forms->reserve(forms->size() + count); 778 for (uint64_t i = 0; i < count; ++i) { 779 scoped_ptr<PasswordForm> form(new PasswordForm()); 780 form->signon_realm.assign(signon_realm); 781 782 int scheme = 0; 783 int64 date_created = 0; 784 int type = 0; 785 // Note that these will be read back in the order listed due to 786 // short-circuit evaluation. This is important. 787 if (!iter.ReadInt(&scheme) || 788 !ReadGURL(&iter, warn_only, &form->origin) || 789 !ReadGURL(&iter, warn_only, &form->action) || 790 !iter.ReadString16(&form->username_element) || 791 !iter.ReadString16(&form->username_value) || 792 !iter.ReadString16(&form->password_element) || 793 !iter.ReadString16(&form->password_value) || 794 !iter.ReadString16(&form->submit_element) || 795 !iter.ReadBool(&form->ssl_valid) || 796 !iter.ReadBool(&form->preferred) || 797 !iter.ReadBool(&form->blacklisted_by_user) || 798 !iter.ReadInt64(&date_created)) { 799 LogDeserializationWarning(version, signon_realm, warn_only); 800 return false; 801 } 802 form->scheme = static_cast<PasswordForm::Scheme>(scheme); 803 form->date_created = base::Time::FromTimeT(date_created); 804 805 if (version > 1) { 806 if (!iter.ReadInt(&type) || 807 !iter.ReadInt(&form->times_used) || 808 !autofill::DeserializeFormData(&iter, &form->form_data)) { 809 LogDeserializationWarning(version, signon_realm, false); 810 return false; 811 } 812 form->type = static_cast<PasswordForm::Type>(type); 813 } 814 815 if (version > 2) { 816 int64 date_synced = 0; 817 if (!iter.ReadInt64(&date_synced)) { 818 LogDeserializationWarning(version, signon_realm, false); 819 return false; 820 } 821 form->date_synced = base::Time::FromInternalValue(date_synced); 822 } 823 824 if (version > 3) { 825 if (!iter.ReadString16(&form->display_name) || 826 !ReadGURL(&iter, warn_only, &form->avatar_url) || 827 !ReadGURL(&iter, warn_only, &form->federation_url) || 828 !iter.ReadBool(&form->is_zero_click)) { 829 LogDeserializationWarning(version, signon_realm, false); 830 return false; 831 } 832 } 833 834 forms->push_back(form.release()); 835 } 836 837 return true; 838 } 839 840 // static 841 void NativeBackendKWallet::DeserializeValue(const std::string& signon_realm, 842 const Pickle& pickle, 843 PasswordFormList* forms) { 844 PickleIterator iter(pickle); 845 846 int version = -1; 847 if (!iter.ReadInt(&version) || 848 version < 0 || version > kPickleVersion) { 849 LOG(ERROR) << "Failed to deserialize KWallet entry " 850 << "(realm: " << signon_realm << ")"; 851 return; 852 } 853 854 if (version > 0) { 855 // In current pickles, we expect 64-bit sizes. Failure is an error. 856 DeserializeValueSize(signon_realm, iter, version, false, false, forms); 857 return; 858 } 859 860 const size_t saved_forms_size = forms->size(); 861 const bool size_32 = sizeof(size_t) == sizeof(uint32_t); 862 if (!DeserializeValueSize( 863 signon_realm, iter, version, size_32, true, forms)) { 864 // We failed to read the pickle using the native architecture of the system. 865 // Try again with the opposite architecture. Note that we do this even on 866 // 32-bit machines, in case we're reading a 64-bit pickle. (Probably rare, 867 // since mostly we expect upgrades, not downgrades, but both are possible.) 868 forms->resize(saved_forms_size); 869 DeserializeValueSize(signon_realm, iter, version, !size_32, false, forms); 870 } 871 } 872 873 int NativeBackendKWallet::WalletHandle() { 874 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 875 876 // Open the wallet. 877 // TODO(mdm): Are we leaking these handles? Find out. 878 int32_t handle = kInvalidKWalletHandle; 879 { 880 dbus::MethodCall method_call(kKWalletInterface, "open"); 881 dbus::MessageWriter builder(&method_call); 882 builder.AppendString(wallet_name_); // wallet 883 builder.AppendInt64(0); // wid 884 builder.AppendString(app_name_); // appid 885 scoped_ptr<dbus::Response> response( 886 kwallet_proxy_->CallMethodAndBlock( 887 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); 888 if (!response.get()) { 889 LOG(ERROR) << "Error contacting kwalletd (open)"; 890 return kInvalidKWalletHandle; 891 } 892 dbus::MessageReader reader(response.get()); 893 if (!reader.PopInt32(&handle)) { 894 LOG(ERROR) << "Error reading response from kwalletd (open): " 895 << response->ToString(); 896 return kInvalidKWalletHandle; 897 } 898 if (handle == kInvalidKWalletHandle) { 899 LOG(ERROR) << "Error obtaining KWallet handle"; 900 return kInvalidKWalletHandle; 901 } 902 } 903 904 // Check if our folder exists. 905 bool has_folder = false; 906 { 907 dbus::MethodCall method_call(kKWalletInterface, "hasFolder"); 908 dbus::MessageWriter builder(&method_call); 909 builder.AppendInt32(handle); // handle 910 builder.AppendString(folder_name_); // folder 911 builder.AppendString(app_name_); // appid 912 scoped_ptr<dbus::Response> response( 913 kwallet_proxy_->CallMethodAndBlock( 914 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); 915 if (!response.get()) { 916 LOG(ERROR) << "Error contacting kwalletd (hasFolder)"; 917 return kInvalidKWalletHandle; 918 } 919 dbus::MessageReader reader(response.get()); 920 if (!reader.PopBool(&has_folder)) { 921 LOG(ERROR) << "Error reading response from kwalletd (hasFolder): " 922 << response->ToString(); 923 return kInvalidKWalletHandle; 924 } 925 } 926 927 // Create it if it didn't. 928 if (!has_folder) { 929 dbus::MethodCall method_call(kKWalletInterface, "createFolder"); 930 dbus::MessageWriter builder(&method_call); 931 builder.AppendInt32(handle); // handle 932 builder.AppendString(folder_name_); // folder 933 builder.AppendString(app_name_); // appid 934 scoped_ptr<dbus::Response> response( 935 kwallet_proxy_->CallMethodAndBlock( 936 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); 937 if (!response.get()) { 938 LOG(ERROR) << "Error contacting kwalletd (createFolder)"; 939 return kInvalidKWalletHandle; 940 } 941 dbus::MessageReader reader(response.get()); 942 bool success = false; 943 if (!reader.PopBool(&success)) { 944 LOG(ERROR) << "Error reading response from kwalletd (createFolder): " 945 << response->ToString(); 946 return kInvalidKWalletHandle; 947 } 948 if (!success) { 949 LOG(ERROR) << "Error creating KWallet folder"; 950 return kInvalidKWalletHandle; 951 } 952 } 953 954 return handle; 955 } 956 957 std::string NativeBackendKWallet::GetProfileSpecificFolderName() const { 958 // Originally, the folder name was always just "Chrome Form Data". 959 // Now we use it to distinguish passwords for different profiles. 960 return base::StringPrintf("%s (%d)", kKWalletFolder, profile_id_); 961 } 962