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