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