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.
5 #include "chrome/browser/password_manager/native_backend_kwallet_x.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"
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"
24 using autofill::PasswordForm;
25 using content::BrowserThread;
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";
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";
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 autofill::PasswordForm& a,
47 const autofill::PasswordForm& b,
49 // An update check doesn't care about the submit element.
50 if (!update_check && a.submit_element != b.submit_element)
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;
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,
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 << ")";
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)) {
80 LOG(ERROR) << "Failed to deserialize URL.";
84 *url = GURL(url_string);
90 NativeBackendKWallet::NativeBackendKWallet(LocalProfileId id,
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;
104 folder_name_ = kKWalletFolder;
105 migrate_tried_ = false;
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()));
123 bool NativeBackendKWallet::Init() {
124 // Without the |optional_bus| parameter, a real bus will be instantiated.
125 return InitWithBus(scoped_refptr<dbus::Bus>());
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));
141 // This ScopedAllowWait should not be here. http://crbug.com/125331
142 base::ThreadRestrictions::ScopedAllowWait allow_wait;
147 void NativeBackendKWallet::InitOnDBThread(scoped_refptr<dbus::Bus> optional_bus,
148 base::WaitableEvent* event,
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;
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);
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));
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));
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";
198 dbus::MessageReader reader(response.get());
200 std::string dbus_name;
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();
209 if (!error.empty() || ret) {
210 LOG(ERROR) << "Error launching kwalletd: error '" << error << "' "
211 << " (code " << ret << ")";
218 NativeBackendKWallet::InitResult NativeBackendKWallet::InitWallet() {
219 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
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;
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;
237 // Not enabled? Don't use KWallet. But also don't warn here.
239 VLOG(1) << "kwalletd reports that KWallet is not enabled.";
240 return PERMANENT_FAIL;
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;
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;
265 bool NativeBackendKWallet::AddLogin(const PasswordForm& form) {
266 int wallet_handle = WalletHandle();
267 if (wallet_handle == kInvalidKWalletHandle)
270 PasswordFormList forms;
271 GetLoginsList(&forms, form.signon_realm, wallet_handle);
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)) {
286 forms.push_back(new PasswordForm(form));
288 bool ok = SetLoginsList(forms, form.signon_realm, wallet_handle);
290 STLDeleteElements(&forms);
294 bool NativeBackendKWallet::UpdateLogin(const PasswordForm& form) {
295 int wallet_handle = WalletHandle();
296 if (wallet_handle == kInvalidKWalletHandle)
299 PasswordFormList forms;
300 GetLoginsList(&forms, form.signon_realm, wallet_handle);
302 for (size_t i = 0; i < forms.size(); ++i) {
303 if (CompareForms(form, *forms[i], true))
307 bool ok = SetLoginsList(forms, form.signon_realm, wallet_handle);
309 STLDeleteElements(&forms);
313 bool NativeBackendKWallet::RemoveLogin(const PasswordForm& form) {
314 int wallet_handle = WalletHandle();
315 if (wallet_handle == kInvalidKWalletHandle)
318 PasswordFormList all_forms;
319 GetLoginsList(&all_forms, form.signon_realm, wallet_handle);
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))
327 kept_forms.push_back(all_forms[i]);
330 // Update the entry in the wallet, possibly deleting it.
331 bool ok = SetLoginsList(kept_forms, form.signon_realm, wallet_handle);
333 STLDeleteElements(&kept_forms);
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)
344 // We could probably also use readEntryList here.
345 std::vector<std::string> realm_list;
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)";
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();
366 while (array.HasMoreData()) {
368 if (!array.PopString(&realm)) {
369 LOG(ERROR) << "Error reading response from kwalletd (entryList): "
370 << response->ToString();
373 realm_list.push_back(realm);
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)";
393 dbus::MessageReader reader(response.get());
394 uint8_t* bytes = NULL;
396 if (!reader.PopArrayOfBytes(&bytes, &length)) {
397 LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
398 << response->ToString();
401 if (!bytes || !CheckSerializedValue(bytes, length, signon_realm))
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);
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)) {
416 kept_forms.push_back(all_forms[i]);
420 if (!SetLoginsList(kept_forms, signon_realm, wallet_handle))
422 STLDeleteElements(&kept_forms);
427 bool NativeBackendKWallet::GetLogins(const PasswordForm& form,
428 PasswordFormList* forms) {
429 int wallet_handle = WalletHandle();
430 if (wallet_handle == kInvalidKWalletHandle)
432 return GetLoginsList(forms, form.signon_realm, wallet_handle);
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)
441 return GetLoginsList(forms, get_begin, get_end, wallet_handle);
444 bool NativeBackendKWallet::GetAutofillableLogins(PasswordFormList* forms) {
445 int wallet_handle = WalletHandle();
446 if (wallet_handle == kInvalidKWalletHandle)
448 return GetLoginsList(forms, true, wallet_handle);
451 bool NativeBackendKWallet::GetBlacklistLogins(PasswordFormList* forms) {
452 int wallet_handle = WalletHandle();
453 if (wallet_handle == kInvalidKWalletHandle)
455 return GetLoginsList(forms, false, wallet_handle);
458 bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms,
459 const std::string& signon_realm,
461 // Is there an entry in the wallet?
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)";
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();
484 // This is not an error. There just isn't a matching entry.
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)";
503 dbus::MessageReader reader(response.get());
504 uint8_t* bytes = NULL;
506 if (!reader.PopArrayOfBytes(&bytes, &length)) {
507 LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
508 << response->ToString();
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).
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);
530 bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms,
533 PasswordFormList all_forms;
534 if (!GetAllLogins(&all_forms, wallet_handle))
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]);
549 bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms,
550 const base::Time& begin,
551 const base::Time& end,
553 PasswordFormList all_forms;
554 if (!GetAllLogins(&all_forms, wallet_handle))
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]);
571 bool NativeBackendKWallet::GetAllLogins(PasswordFormList* forms,
573 // We could probably also use readEntryList here.
574 std::vector<std::string> realm_list;
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)";
588 dbus::MessageReader reader(response.get());
589 if (!reader.PopArrayOfStrings(&realm_list)) {
590 LOG(ERROR) << "Error reading response from kwalletd (entryList): "
591 << response->ToString();
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)";
611 dbus::MessageReader reader(response.get());
612 uint8_t* bytes = NULL;
614 if (!reader.PopArrayOfBytes(&bytes, &length)) {
615 LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
616 << response->ToString();
619 if (!bytes || !CheckSerializedValue(bytes, length, signon_realm))
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);
630 bool NativeBackendKWallet::SetLoginsList(const PasswordFormList& forms,
631 const std::string& signon_realm,
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;
648 dbus::MessageReader reader(response.get());
650 if (!reader.PopInt32(&ret)) {
651 LOG(ERROR) << "Error reading response from kwalletd (removeEntry): "
652 << response->ToString();
656 LOG(ERROR) << "Bad return code " << ret << " from KWallet removeEntry";
661 SerializeValue(forms, &value);
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;
678 dbus::MessageReader reader(response.get());
680 if (!reader.PopInt32(&ret)) {
681 LOG(ERROR) << "Error reading response from kwalletd (writeEntry): "
682 << response->ToString();
686 LOG(ERROR) << "Bad return code " << ret << " from KWallet writeEntry";
691 void NativeBackendKWallet::SerializeValue(const PasswordFormList& forms,
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());
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;
722 uint32_t count_32 = 0;
723 if (!iter.ReadUInt32(&count_32)) {
724 LOG(ERROR) << "Failed to deserialize KWallet entry "
725 << "(realm: " << signon_realm << ")";
730 if (!iter.ReadUInt64(&count)) {
731 LOG(ERROR) << "Failed to deserialize KWallet entry "
732 << "(realm: " << signon_realm << ")";
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.)
745 LOG(ERROR) << "Suspiciously large number of entries in KWallet entry "
746 << "(" << count << "; realm: " << signon_realm << ")";
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);
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)) {
773 LOG(WARNING) << "Failed to deserialize version 0 KWallet entry "
774 << "(realm: " << signon_realm << ") with native "
775 << "architecture size; will try alternate size.";
777 LOG(ERROR) << "Failed to deserialize KWallet entry "
778 << "(realm: " << signon_realm << ")";
782 form->scheme = static_cast<PasswordForm::Scheme>(scheme);
783 form->date_created = base::Time::FromTimeT(date_created);
784 forms->push_back(form.release());
791 void NativeBackendKWallet::DeserializeValue(const std::string& signon_realm,
792 const Pickle& pickle,
793 PasswordFormList* forms) {
794 PickleIterator iter(pickle);
797 if (!iter.ReadInt(&version) ||
798 version < 0 || version > kPickleVersion) {
799 LOG(ERROR) << "Failed to deserialize KWallet entry "
800 << "(realm: " << signon_realm << ")";
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);
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);
822 int NativeBackendKWallet::WalletHandle() {
823 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
826 // TODO(mdm): Are we leaking these handles? Find out.
827 int32_t handle = kInvalidKWalletHandle;
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;
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;
847 if (handle == kInvalidKWalletHandle) {
848 LOG(ERROR) << "Error obtaining KWallet handle";
849 return kInvalidKWalletHandle;
853 // Check if our folder exists.
854 bool has_folder = false;
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;
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;
876 // Create it if it didn't.
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;
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;
898 LOG(ERROR) << "Error creating KWallet folder";
899 return kInvalidKWalletHandle;
903 // Successful initialization. Try migration if necessary.
905 MigrateToProfileSpecificLogins();
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_);
916 void NativeBackendKWallet::MigrateToProfileSpecificLogins() {
917 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
919 DCHECK(!migrate_tried_);
920 DCHECK_EQ(folder_name_, kKWalletFolder);
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;
926 // First get all the logins, using the old folder name.
927 int wallet_handle = WalletHandle();
928 if (wallet_handle == kInvalidKWalletHandle)
930 PasswordFormList forms;
931 if (!GetAllLogins(&forms, wallet_handle))
934 // Now switch to a profile-specific folder name.
935 folder_name_ = GetProfileSpecificFolderName();
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.
943 for (size_t i = 0; i < forms.size(); ++i) {
944 if (!AddLogin(*forms[i]))
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)
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_);
967 // We failed to migrate for some reason. Use the old folder name.
968 folder_name_ = kKWalletFolder;