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/metrics/histogram.h"
12 #include "base/pickle.h"
13 #include "base/stl_util.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/synchronization/waitable_event.h"
16 #include "base/threading/thread_restrictions.h"
17 #include "chrome/grit/chromium_strings.h"
18 #include "components/autofill/core/common/password_form.h"
19 #include "content/public/browser/browser_thread.h"
21 #include "dbus/message.h"
22 #include "dbus/object_path.h"
23 #include "dbus/object_proxy.h"
24 #include "ui/base/l10n/l10n_util.h"
26 using autofill::PasswordForm
;
27 using content::BrowserThread
;
31 // In case the fields in the pickle ever change, version them so we can try to
32 // read old pickles. (Note: do not eat old pickles past the expiration date.)
33 const int kPickleVersion
= 6;
35 // We could localize this string, but then changing your locale would cause
36 // you to lose access to all your stored passwords. Maybe best not to do that.
37 // Name of the folder to store passwords in.
38 const char kKWalletFolder
[] = "Chrome Form Data";
40 // DBus service, path, and interface names for klauncher and kwalletd.
41 const char kKWalletServiceName
[] = "org.kde.kwalletd";
42 const char kKWalletPath
[] = "/modules/kwalletd";
43 const char kKWalletInterface
[] = "org.kde.KWallet";
44 const char kKLauncherServiceName
[] = "org.kde.klauncher";
45 const char kKLauncherPath
[] = "/KLauncher";
46 const char kKLauncherInterface
[] = "org.kde.KLauncher";
48 // Compares two PasswordForms and returns true if they are the same.
49 // If |update_check| is false, we only check the fields that are checked by
50 // LoginDatabase::UpdateLogin() when updating logins; otherwise, we check the
51 // fields that are checked by LoginDatabase::RemoveLogin() for removing them.
52 bool CompareForms(const autofill::PasswordForm
& a
,
53 const autofill::PasswordForm
& b
,
55 // An update check doesn't care about the submit element.
56 if (!update_check
&& a
.submit_element
!= b
.submit_element
)
58 return a
.origin
== b
.origin
&&
59 a
.password_element
== b
.password_element
&&
60 a
.signon_realm
== b
.signon_realm
&&
61 a
.username_element
== b
.username_element
&&
62 a
.username_value
== b
.username_value
;
65 // Checks a serialized list of PasswordForms for sanity. Returns true if OK.
66 // Note that |realm| is only used for generating a useful warning message.
67 bool CheckSerializedValue(const uint8_t* byte_array
,
69 const std::string
& realm
) {
70 const Pickle::Header
* header
=
71 reinterpret_cast<const Pickle::Header
*>(byte_array
);
72 if (length
< sizeof(*header
) ||
73 header
->payload_size
> length
- sizeof(*header
)) {
74 LOG(WARNING
) << "Invalid KWallet entry detected (realm: " << realm
<< ")";
80 // Convenience function to read a GURL from a Pickle. Assumes the URL has
81 // been written as a UTF-8 string. Returns true on success.
82 bool ReadGURL(PickleIterator
* iter
, bool warn_only
, GURL
* url
) {
83 std::string url_string
;
84 if (!iter
->ReadString(&url_string
)) {
86 LOG(ERROR
) << "Failed to deserialize URL.";
90 *url
= GURL(url_string
);
94 void LogDeserializationWarning(int version
,
95 std::string signon_realm
,
98 LOG(WARNING
) << "Failed to deserialize version " << version
99 << " KWallet entry (realm: " << signon_realm
100 << ") with native architecture size; will try alternate "
103 LOG(ERROR
) << "Failed to deserialize version " << version
104 << " KWallet entry (realm: " << signon_realm
<< ")";
108 // Deserializes a list of credentials from the wallet to |forms| (replacing
109 // the contents of |forms|). |size_32| controls reading the size field within
110 // the pickle as 32 bits. We used to use Pickle::WriteSize() to write the number
111 // of password forms, but that has a different size on 32- and 64-bit systems.
112 // So, now we always write a 64-bit quantity, but we support trying to read it
113 // as either size when reading old pickles that fail to deserialize using the
114 // native size. Returns true on success.
115 bool DeserializeValueSize(const std::string
& signon_realm
,
116 const PickleIterator
& init_iter
,
120 ScopedVector
<autofill::PasswordForm
>* forms
) {
121 PickleIterator iter
= init_iter
;
125 uint32_t count_32
= 0;
126 if (!iter
.ReadUInt32(&count_32
)) {
127 LOG(ERROR
) << "Failed to deserialize KWallet entry "
128 << "(realm: " << signon_realm
<< ")";
133 if (!iter
.ReadSizeT(&count
)) {
134 LOG(ERROR
) << "Failed to deserialize KWallet entry "
135 << "(realm: " << signon_realm
<< ")";
140 if (count
> 0xFFFF) {
141 // Trying to pin down the cause of http://crbug.com/80728 (or fix it).
142 // This is a very large number of passwords to be saved for a single realm.
143 // It is almost certainly a corrupt pickle and not real data. Ignore it.
144 // This very well might actually be http://crbug.com/107701, so if we're
145 // reading an old pickle, we don't even log this the first time we try to
146 // read it. (That is, when we're reading the native architecture size.)
148 LOG(ERROR
) << "Suspiciously large number of entries in KWallet entry "
149 << "(" << count
<< "; realm: " << signon_realm
<< ")";
154 // We'll swap |converted_forms| with |*forms| on success, to make sure we
155 // don't return partial results on failure.
156 ScopedVector
<autofill::PasswordForm
> converted_forms
;
157 converted_forms
.reserve(count
);
158 for (size_t i
= 0; i
< count
; ++i
) {
159 scoped_ptr
<PasswordForm
> form(new PasswordForm());
160 form
->signon_realm
.assign(signon_realm
);
163 int64 date_created
= 0;
165 int generation_upload_status
= 0;
166 // Note that these will be read back in the order listed due to
167 // short-circuit evaluation. This is important.
168 if (!iter
.ReadInt(&scheme
) ||
169 !ReadGURL(&iter
, warn_only
, &form
->origin
) ||
170 !ReadGURL(&iter
, warn_only
, &form
->action
) ||
171 !iter
.ReadString16(&form
->username_element
) ||
172 !iter
.ReadString16(&form
->username_value
) ||
173 !iter
.ReadString16(&form
->password_element
) ||
174 !iter
.ReadString16(&form
->password_value
) ||
175 !iter
.ReadString16(&form
->submit_element
) ||
176 !iter
.ReadBool(&form
->ssl_valid
) ||
177 !iter
.ReadBool(&form
->preferred
) ||
178 !iter
.ReadBool(&form
->blacklisted_by_user
) ||
179 !iter
.ReadInt64(&date_created
)) {
180 LogDeserializationWarning(version
, signon_realm
, warn_only
);
183 form
->scheme
= static_cast<PasswordForm::Scheme
>(scheme
);
186 if (!iter
.ReadInt(&type
) ||
187 !iter
.ReadInt(&form
->times_used
) ||
188 !autofill::DeserializeFormData(&iter
, &form
->form_data
)) {
189 LogDeserializationWarning(version
, signon_realm
, false);
192 form
->type
= static_cast<PasswordForm::Type
>(type
);
196 int64 date_synced
= 0;
197 if (!iter
.ReadInt64(&date_synced
)) {
198 LogDeserializationWarning(version
, signon_realm
, false);
201 form
->date_synced
= base::Time::FromInternalValue(date_synced
);
205 if (!iter
.ReadString16(&form
->display_name
) ||
206 !ReadGURL(&iter
, warn_only
, &form
->avatar_url
) ||
207 !ReadGURL(&iter
, warn_only
, &form
->federation_url
) ||
208 !iter
.ReadBool(&form
->skip_zero_click
)) {
209 LogDeserializationWarning(version
, signon_realm
, false);
215 form
->date_created
= base::Time::FromInternalValue(date_created
);
217 form
->date_created
= base::Time::FromTimeT(date_created
);
221 if (!iter
.ReadInt(&generation_upload_status
)) {
222 LogDeserializationWarning(version
, signon_realm
, false);
224 form
->generation_upload_status
=
225 static_cast<PasswordForm::GenerationUploadStatus
>(
226 generation_upload_status
);
229 converted_forms
.push_back(form
.release());
232 forms
->swap(converted_forms
);
236 // Serializes a list of PasswordForms to be stored in the wallet.
237 void SerializeValue(const std::vector
<autofill::PasswordForm
*>& forms
,
239 pickle
->WriteInt(kPickleVersion
);
240 pickle
->WriteSizeT(forms
.size());
241 for (autofill::PasswordForm
* form
: forms
) {
242 pickle
->WriteInt(form
->scheme
);
243 pickle
->WriteString(form
->origin
.spec());
244 pickle
->WriteString(form
->action
.spec());
245 pickle
->WriteString16(form
->username_element
);
246 pickle
->WriteString16(form
->username_value
);
247 pickle
->WriteString16(form
->password_element
);
248 pickle
->WriteString16(form
->password_value
);
249 pickle
->WriteString16(form
->submit_element
);
250 pickle
->WriteBool(form
->ssl_valid
);
251 pickle
->WriteBool(form
->preferred
);
252 pickle
->WriteBool(form
->blacklisted_by_user
);
253 pickle
->WriteInt64(form
->date_created
.ToInternalValue());
254 pickle
->WriteInt(form
->type
);
255 pickle
->WriteInt(form
->times_used
);
256 autofill::SerializeFormData(form
->form_data
, pickle
);
257 pickle
->WriteInt64(form
->date_synced
.ToInternalValue());
258 pickle
->WriteString16(form
->display_name
);
259 pickle
->WriteString(form
->avatar_url
.spec());
260 pickle
->WriteString(form
->federation_url
.spec());
261 pickle
->WriteBool(form
->skip_zero_click
);
265 // Moves the content of |second| to the end of |first|.
266 void AppendSecondToFirst(ScopedVector
<autofill::PasswordForm
>* first
,
267 ScopedVector
<autofill::PasswordForm
> second
) {
268 first
->reserve(first
->size() + second
.size());
269 first
->insert(first
->end(), second
.begin(), second
.end());
273 void UMALogDeserializationStatus(bool success
) {
274 UMA_HISTOGRAM_BOOLEAN("PasswordManager.KWalletDeserializationStatus",
280 NativeBackendKWallet::NativeBackendKWallet(LocalProfileId id
)
282 kwallet_proxy_(nullptr),
283 app_name_(l10n_util::GetStringUTF8(IDS_PRODUCT_NAME
)) {
284 folder_name_
= GetProfileSpecificFolderName();
287 NativeBackendKWallet::~NativeBackendKWallet() {
288 // This destructor is called on the thread that is destroying the Profile
289 // containing the PasswordStore that owns this NativeBackend. Generally that
290 // won't be the DB thread; it will be the UI thread. So we post a message to
291 // shut it down on the DB thread, and it will be destructed afterward when the
292 // scoped_refptr<dbus::Bus> goes out of scope. The NativeBackend will be
293 // destroyed before that occurs, but that's OK.
294 if (session_bus_
.get()) {
295 BrowserThread::PostTask(BrowserThread::DB
, FROM_HERE
,
296 base::Bind(&dbus::Bus::ShutdownAndBlock
,
297 session_bus_
.get()));
301 bool NativeBackendKWallet::Init() {
302 // Without the |optional_bus| parameter, a real bus will be instantiated.
303 return InitWithBus(scoped_refptr
<dbus::Bus
>());
306 bool NativeBackendKWallet::InitWithBus(scoped_refptr
<dbus::Bus
> optional_bus
) {
307 // We must synchronously do a few DBus calls to figure out if initialization
308 // succeeds, but later, we'll want to do most work on the DB thread. So we
309 // have to do the initialization on the DB thread here too, and wait for it.
310 bool success
= false;
311 base::WaitableEvent
event(false, false);
312 // NativeBackendKWallet isn't reference counted, but we wait for InitWithBus
313 // to finish, so we can safely use base::Unretained here.
314 BrowserThread::PostTask(BrowserThread::DB
, FROM_HERE
,
315 base::Bind(&NativeBackendKWallet::InitOnDBThread
,
316 base::Unretained(this),
317 optional_bus
, &event
, &success
));
319 // This ScopedAllowWait should not be here. http://crbug.com/125331
320 base::ThreadRestrictions::ScopedAllowWait allow_wait
;
325 void NativeBackendKWallet::InitOnDBThread(scoped_refptr
<dbus::Bus
> optional_bus
,
326 base::WaitableEvent
* event
,
328 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
329 DCHECK(!session_bus_
.get());
330 if (optional_bus
.get()) {
331 // The optional_bus parameter is given when this method is called in tests.
332 session_bus_
= optional_bus
;
334 // Get a (real) connection to the session bus.
335 dbus::Bus::Options options
;
336 options
.bus_type
= dbus::Bus::SESSION
;
337 options
.connection_type
= dbus::Bus::PRIVATE
;
338 session_bus_
= new dbus::Bus(options
);
341 session_bus_
->GetObjectProxy(kKWalletServiceName
,
342 dbus::ObjectPath(kKWalletPath
));
343 // kwalletd may not be running. If we get a temporary failure initializing it,
344 // try to start it and then try again. (Note the short-circuit evaluation.)
345 const InitResult result
= InitWallet();
346 *success
= (result
== INIT_SUCCESS
||
347 (result
== TEMPORARY_FAIL
&&
348 StartKWalletd() && InitWallet() == INIT_SUCCESS
));
352 bool NativeBackendKWallet::StartKWalletd() {
353 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
354 // Sadly kwalletd doesn't use DBus activation, so we have to make a call to
355 // klauncher to start it.
356 dbus::ObjectProxy
* klauncher
=
357 session_bus_
->GetObjectProxy(kKLauncherServiceName
,
358 dbus::ObjectPath(kKLauncherPath
));
360 dbus::MethodCall
method_call(kKLauncherInterface
,
361 "start_service_by_desktop_name");
362 dbus::MessageWriter
builder(&method_call
);
363 std::vector
<std::string
> empty
;
364 builder
.AppendString("kwalletd"); // serviceName
365 builder
.AppendArrayOfStrings(empty
); // urls
366 builder
.AppendArrayOfStrings(empty
); // envs
367 builder
.AppendString(std::string()); // startup_id
368 builder
.AppendBool(false); // blind
369 scoped_ptr
<dbus::Response
> response(
370 klauncher
->CallMethodAndBlock(
371 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
372 if (!response
.get()) {
373 LOG(ERROR
) << "Error contacting klauncher to start kwalletd";
376 dbus::MessageReader
reader(response
.get());
378 std::string dbus_name
;
381 if (!reader
.PopInt32(&ret
) || !reader
.PopString(&dbus_name
) ||
382 !reader
.PopString(&error
) || !reader
.PopInt32(&pid
)) {
383 LOG(ERROR
) << "Error reading response from klauncher to start kwalletd: "
384 << response
->ToString();
387 if (!error
.empty() || ret
) {
388 LOG(ERROR
) << "Error launching kwalletd: error '" << error
<< "' "
389 << " (code " << ret
<< ")";
396 NativeBackendKWallet::InitResult
NativeBackendKWallet::InitWallet() {
397 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
399 // Check that KWallet is enabled.
400 dbus::MethodCall
method_call(kKWalletInterface
, "isEnabled");
401 scoped_ptr
<dbus::Response
> response(
402 kwallet_proxy_
->CallMethodAndBlock(
403 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
404 if (!response
.get()) {
405 LOG(ERROR
) << "Error contacting kwalletd (isEnabled)";
406 return TEMPORARY_FAIL
;
408 dbus::MessageReader
reader(response
.get());
409 bool enabled
= false;
410 if (!reader
.PopBool(&enabled
)) {
411 LOG(ERROR
) << "Error reading response from kwalletd (isEnabled): "
412 << response
->ToString();
413 return PERMANENT_FAIL
;
415 // Not enabled? Don't use KWallet. But also don't warn here.
417 VLOG(1) << "kwalletd reports that KWallet is not enabled.";
418 return PERMANENT_FAIL
;
423 // Get the wallet name.
424 dbus::MethodCall
method_call(kKWalletInterface
, "networkWallet");
425 scoped_ptr
<dbus::Response
> response(
426 kwallet_proxy_
->CallMethodAndBlock(
427 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
428 if (!response
.get()) {
429 LOG(ERROR
) << "Error contacting kwalletd (networkWallet)";
430 return TEMPORARY_FAIL
;
432 dbus::MessageReader
reader(response
.get());
433 if (!reader
.PopString(&wallet_name_
)) {
434 LOG(ERROR
) << "Error reading response from kwalletd (networkWallet): "
435 << response
->ToString();
436 return PERMANENT_FAIL
;
443 password_manager::PasswordStoreChangeList
NativeBackendKWallet::AddLogin(
444 const PasswordForm
& form
) {
445 int wallet_handle
= WalletHandle();
446 if (wallet_handle
== kInvalidKWalletHandle
)
447 return password_manager::PasswordStoreChangeList();
449 ScopedVector
<autofill::PasswordForm
> forms
;
450 if (!GetLoginsList(form
.signon_realm
, wallet_handle
, &forms
))
451 return password_manager::PasswordStoreChangeList();
453 // We search for a login to update, rather than unconditionally appending the
454 // login, because in some cases (especially involving sync) we can be asked to
455 // add a login that already exists. In these cases we want to just update.
456 bool updated
= false;
457 password_manager::PasswordStoreChangeList changes
;
458 for (size_t i
= 0; i
< forms
.size(); ++i
) {
459 // Use the more restrictive removal comparison, so that we never have
460 // duplicate logins that would all be removed together by RemoveLogin().
461 if (CompareForms(form
, *forms
[i
], false)) {
462 changes
.push_back(password_manager::PasswordStoreChange(
463 password_manager::PasswordStoreChange::REMOVE
, *forms
[i
]));
469 forms
.push_back(new PasswordForm(form
));
470 changes
.push_back(password_manager::PasswordStoreChange(
471 password_manager::PasswordStoreChange::ADD
, form
));
473 bool ok
= SetLoginsList(forms
.get(), form
.signon_realm
, wallet_handle
);
480 bool NativeBackendKWallet::UpdateLogin(
481 const PasswordForm
& form
,
482 password_manager::PasswordStoreChangeList
* changes
) {
485 int wallet_handle
= WalletHandle();
486 if (wallet_handle
== kInvalidKWalletHandle
)
489 ScopedVector
<autofill::PasswordForm
> forms
;
490 if (!GetLoginsList(form
.signon_realm
, wallet_handle
, &forms
))
493 bool updated
= false;
494 for (size_t i
= 0; i
< forms
.size(); ++i
) {
495 if (CompareForms(form
, *forms
[i
], true)) {
503 if (SetLoginsList(forms
.get(), form
.signon_realm
, wallet_handle
)) {
504 changes
->push_back(password_manager::PasswordStoreChange(
505 password_manager::PasswordStoreChange::UPDATE
, form
));
512 bool NativeBackendKWallet::RemoveLogin(const PasswordForm
& form
) {
513 int wallet_handle
= WalletHandle();
514 if (wallet_handle
== kInvalidKWalletHandle
)
517 ScopedVector
<autofill::PasswordForm
> all_forms
;
518 if (!GetLoginsList(form
.signon_realm
, wallet_handle
, &all_forms
))
521 ScopedVector
<autofill::PasswordForm
> kept_forms
;
522 kept_forms
.reserve(all_forms
.size());
523 for (auto& saved_form
: all_forms
) {
524 if (!CompareForms(form
, *saved_form
, false)) {
525 kept_forms
.push_back(saved_form
);
526 saved_form
= nullptr;
530 // Update the entry in the wallet, possibly deleting it.
531 return SetLoginsList(kept_forms
.get(), form
.signon_realm
, wallet_handle
);
534 bool NativeBackendKWallet::RemoveLoginsCreatedBetween(
535 base::Time delete_begin
,
536 base::Time delete_end
,
537 password_manager::PasswordStoreChangeList
* changes
) {
538 return RemoveLoginsBetween(
539 delete_begin
, delete_end
, CREATION_TIMESTAMP
, changes
);
542 bool NativeBackendKWallet::RemoveLoginsSyncedBetween(
543 base::Time delete_begin
,
544 base::Time delete_end
,
545 password_manager::PasswordStoreChangeList
* changes
) {
546 return RemoveLoginsBetween(delete_begin
, delete_end
, SYNC_TIMESTAMP
, changes
);
549 bool NativeBackendKWallet::GetLogins(
550 const PasswordForm
& form
,
551 ScopedVector
<autofill::PasswordForm
>* forms
) {
552 int wallet_handle
= WalletHandle();
553 if (wallet_handle
== kInvalidKWalletHandle
)
555 return GetLoginsList(form
.signon_realm
, wallet_handle
, forms
);
558 bool NativeBackendKWallet::GetAutofillableLogins(
559 ScopedVector
<autofill::PasswordForm
>* forms
) {
560 int wallet_handle
= WalletHandle();
561 if (wallet_handle
== kInvalidKWalletHandle
)
563 return GetLoginsList(BlacklistOptions::AUTOFILLABLE
, wallet_handle
, forms
);
566 bool NativeBackendKWallet::GetBlacklistLogins(
567 ScopedVector
<autofill::PasswordForm
>* forms
) {
568 int wallet_handle
= WalletHandle();
569 if (wallet_handle
== kInvalidKWalletHandle
)
571 return GetLoginsList(BlacklistOptions::BLACKLISTED
, wallet_handle
, forms
);
574 bool NativeBackendKWallet::GetLoginsList(
575 const std::string
& signon_realm
,
577 ScopedVector
<autofill::PasswordForm
>* forms
) {
579 // Is there an entry in the wallet?
581 dbus::MethodCall
method_call(kKWalletInterface
, "hasEntry");
582 dbus::MessageWriter
builder(&method_call
);
583 builder
.AppendInt32(wallet_handle
); // handle
584 builder
.AppendString(folder_name_
); // folder
585 builder
.AppendString(signon_realm
); // key
586 builder
.AppendString(app_name_
); // appid
587 scoped_ptr
<dbus::Response
> response(
588 kwallet_proxy_
->CallMethodAndBlock(
589 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
590 if (!response
.get()) {
591 LOG(ERROR
) << "Error contacting kwalletd (hasEntry)";
594 dbus::MessageReader
reader(response
.get());
595 bool has_entry
= false;
596 if (!reader
.PopBool(&has_entry
)) {
597 LOG(ERROR
) << "Error reading response from kwalletd (hasEntry): "
598 << response
->ToString();
602 // This is not an error. There just isn't a matching entry.
608 dbus::MethodCall
method_call(kKWalletInterface
, "readEntry");
609 dbus::MessageWriter
builder(&method_call
);
610 builder
.AppendInt32(wallet_handle
); // handle
611 builder
.AppendString(folder_name_
); // folder
612 builder
.AppendString(signon_realm
); // key
613 builder
.AppendString(app_name_
); // appid
614 scoped_ptr
<dbus::Response
> response(
615 kwallet_proxy_
->CallMethodAndBlock(
616 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
617 if (!response
.get()) {
618 LOG(ERROR
) << "Error contacting kwalletd (readEntry)";
621 dbus::MessageReader
reader(response
.get());
622 const uint8_t* bytes
= nullptr;
624 if (!reader
.PopArrayOfBytes(&bytes
, &length
)) {
625 LOG(ERROR
) << "Error reading response from kwalletd (readEntry): "
626 << response
->ToString();
631 if (!CheckSerializedValue(bytes
, length
, signon_realm
)) {
632 // This is weird, but we choose not to call it an error. There is an
633 // invalid entry somehow, but by just ignoring it, we make it easier to
634 // repair without having to delete it using kwalletmanager (that is, by
635 // just saving a new password within this realm to overwrite it).
639 // Can't we all just agree on whether bytes are signed or not? Please?
640 Pickle
pickle(reinterpret_cast<const char*>(bytes
), length
);
641 *forms
= DeserializeValue(signon_realm
, pickle
);
647 bool NativeBackendKWallet::GetLoginsList(
648 BlacklistOptions options
,
650 ScopedVector
<autofill::PasswordForm
>* forms
) {
652 ScopedVector
<autofill::PasswordForm
> all_forms
;
653 if (!GetAllLogins(wallet_handle
, &all_forms
))
656 // We have to read all the entries, and then filter them here.
657 forms
->reserve(all_forms
.size());
658 for (auto& saved_form
: all_forms
) {
659 if (saved_form
->blacklisted_by_user
==
660 (options
== BlacklistOptions::BLACKLISTED
)) {
661 forms
->push_back(saved_form
);
662 saved_form
= nullptr;
669 bool NativeBackendKWallet::GetAllLogins(
671 ScopedVector
<autofill::PasswordForm
>* forms
) {
672 // We could probably also use readEntryList here.
673 std::vector
<std::string
> realm_list
;
675 dbus::MethodCall
method_call(kKWalletInterface
, "entryList");
676 dbus::MessageWriter
builder(&method_call
);
677 builder
.AppendInt32(wallet_handle
); // handle
678 builder
.AppendString(folder_name_
); // folder
679 builder
.AppendString(app_name_
); // appid
680 scoped_ptr
<dbus::Response
> response(
681 kwallet_proxy_
->CallMethodAndBlock(
682 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
683 if (!response
.get()) {
684 LOG(ERROR
) << "Error contacting kwalletd (entryList)";
687 dbus::MessageReader
reader(response
.get());
688 if (!reader
.PopArrayOfStrings(&realm_list
)) {
689 LOG(ERROR
) << "Error reading response from kwalletd (entryList): "
690 << response
->ToString();
696 for (const std::string
& signon_realm
: realm_list
) {
697 dbus::MethodCall
method_call(kKWalletInterface
, "readEntry");
698 dbus::MessageWriter
builder(&method_call
);
699 builder
.AppendInt32(wallet_handle
); // handle
700 builder
.AppendString(folder_name_
); // folder
701 builder
.AppendString(signon_realm
); // key
702 builder
.AppendString(app_name_
); // appid
703 scoped_ptr
<dbus::Response
> response(
704 kwallet_proxy_
->CallMethodAndBlock(
705 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
706 if (!response
.get()) {
707 LOG(ERROR
) << "Error contacting kwalletd (readEntry)";
710 dbus::MessageReader
reader(response
.get());
711 const uint8_t* bytes
= nullptr;
713 if (!reader
.PopArrayOfBytes(&bytes
, &length
)) {
714 LOG(ERROR
) << "Error reading response from kwalletd (readEntry): "
715 << response
->ToString();
718 if (!bytes
|| !CheckSerializedValue(bytes
, length
, signon_realm
))
721 // Can't we all just agree on whether bytes are signed or not? Please?
722 Pickle
pickle(reinterpret_cast<const char*>(bytes
), length
);
723 AppendSecondToFirst(forms
, DeserializeValue(signon_realm
, pickle
));
728 bool NativeBackendKWallet::SetLoginsList(
729 const std::vector
<autofill::PasswordForm
*>& forms
,
730 const std::string
& signon_realm
,
733 // No items left? Remove the entry from the wallet.
734 dbus::MethodCall
method_call(kKWalletInterface
, "removeEntry");
735 dbus::MessageWriter
builder(&method_call
);
736 builder
.AppendInt32(wallet_handle
); // handle
737 builder
.AppendString(folder_name_
); // folder
738 builder
.AppendString(signon_realm
); // key
739 builder
.AppendString(app_name_
); // appid
740 scoped_ptr
<dbus::Response
> response(
741 kwallet_proxy_
->CallMethodAndBlock(
742 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
743 if (!response
.get()) {
744 LOG(ERROR
) << "Error contacting kwalletd (removeEntry)";
745 return kInvalidKWalletHandle
;
747 dbus::MessageReader
reader(response
.get());
749 if (!reader
.PopInt32(&ret
)) {
750 LOG(ERROR
) << "Error reading response from kwalletd (removeEntry): "
751 << response
->ToString();
755 LOG(ERROR
) << "Bad return code " << ret
<< " from KWallet removeEntry";
760 SerializeValue(forms
, &value
);
762 dbus::MethodCall
method_call(kKWalletInterface
, "writeEntry");
763 dbus::MessageWriter
builder(&method_call
);
764 builder
.AppendInt32(wallet_handle
); // handle
765 builder
.AppendString(folder_name_
); // folder
766 builder
.AppendString(signon_realm
); // key
767 builder
.AppendArrayOfBytes(static_cast<const uint8_t*>(value
.data()),
768 value
.size()); // value
769 builder
.AppendString(app_name_
); // appid
770 scoped_ptr
<dbus::Response
> response(
771 kwallet_proxy_
->CallMethodAndBlock(
772 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
773 if (!response
.get()) {
774 LOG(ERROR
) << "Error contacting kwalletd (writeEntry)";
775 return kInvalidKWalletHandle
;
777 dbus::MessageReader
reader(response
.get());
779 if (!reader
.PopInt32(&ret
)) {
780 LOG(ERROR
) << "Error reading response from kwalletd (writeEntry): "
781 << response
->ToString();
785 LOG(ERROR
) << "Bad return code " << ret
<< " from KWallet writeEntry";
789 bool NativeBackendKWallet::RemoveLoginsBetween(
790 base::Time delete_begin
,
791 base::Time delete_end
,
792 TimestampToCompare date_to_compare
,
793 password_manager::PasswordStoreChangeList
* changes
) {
796 int wallet_handle
= WalletHandle();
797 if (wallet_handle
== kInvalidKWalletHandle
)
800 // We could probably also use readEntryList here.
801 std::vector
<std::string
> realm_list
;
803 dbus::MethodCall
method_call(kKWalletInterface
, "entryList");
804 dbus::MessageWriter
builder(&method_call
);
805 builder
.AppendInt32(wallet_handle
); // handle
806 builder
.AppendString(folder_name_
); // folder
807 builder
.AppendString(app_name_
); // appid
808 scoped_ptr
<dbus::Response
> response(kwallet_proxy_
->CallMethodAndBlock(
809 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
810 if (!response
.get()) {
811 LOG(ERROR
) << "Error contacting kwalletd (entryList)";
814 dbus::MessageReader
reader(response
.get());
815 dbus::MessageReader
array(response
.get());
816 if (!reader
.PopArray(&array
)) {
817 LOG(ERROR
) << "Error reading response from kwalletd (entryList): "
818 << response
->ToString();
821 while (array
.HasMoreData()) {
823 if (!array
.PopString(&realm
)) {
824 LOG(ERROR
) << "Error reading response from kwalletd (entryList): "
825 << response
->ToString();
828 realm_list
.push_back(realm
);
833 for (size_t i
= 0; i
< realm_list
.size(); ++i
) {
834 const std::string
& signon_realm
= realm_list
[i
];
835 dbus::MethodCall
method_call(kKWalletInterface
, "readEntry");
836 dbus::MessageWriter
builder(&method_call
);
837 builder
.AppendInt32(wallet_handle
); // handle
838 builder
.AppendString(folder_name_
); // folder
839 builder
.AppendString(signon_realm
); // key
840 builder
.AppendString(app_name_
); // appid
841 scoped_ptr
<dbus::Response
> response(kwallet_proxy_
->CallMethodAndBlock(
842 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
843 if (!response
.get()) {
844 LOG(ERROR
) << "Error contacting kwalletd (readEntry)";
847 dbus::MessageReader
reader(response
.get());
848 const uint8_t* bytes
= nullptr;
850 if (!reader
.PopArrayOfBytes(&bytes
, &length
)) {
851 LOG(ERROR
) << "Error reading response from kwalletd (readEntry): "
852 << response
->ToString();
855 if (!bytes
|| !CheckSerializedValue(bytes
, length
, signon_realm
))
858 // Can't we all just agree on whether bytes are signed or not? Please?
859 Pickle
pickle(reinterpret_cast<const char*>(bytes
), length
);
860 ScopedVector
<autofill::PasswordForm
> all_forms
=
861 DeserializeValue(signon_realm
, pickle
);
863 ScopedVector
<autofill::PasswordForm
> kept_forms
;
864 kept_forms
.reserve(all_forms
.size());
865 base::Time
autofill::PasswordForm::*date_member
=
866 date_to_compare
== CREATION_TIMESTAMP
867 ? &autofill::PasswordForm::date_created
868 : &autofill::PasswordForm::date_synced
;
869 for (auto& saved_form
: all_forms
) {
870 if (delete_begin
<= saved_form
->*date_member
&&
871 (delete_end
.is_null() || saved_form
->*date_member
< delete_end
)) {
872 changes
->push_back(password_manager::PasswordStoreChange(
873 password_manager::PasswordStoreChange::REMOVE
, *saved_form
));
875 kept_forms
.push_back(saved_form
);
876 saved_form
= nullptr;
880 if (!SetLoginsList(kept_forms
.get(), signon_realm
, wallet_handle
)) {
889 ScopedVector
<autofill::PasswordForm
> NativeBackendKWallet::DeserializeValue(
890 const std::string
& signon_realm
,
891 const Pickle
& pickle
) {
892 PickleIterator
iter(pickle
);
895 if (!iter
.ReadInt(&version
) ||
896 version
< 0 || version
> kPickleVersion
) {
897 LOG(ERROR
) << "Failed to deserialize KWallet entry "
898 << "(realm: " << signon_realm
<< ")";
899 return ScopedVector
<autofill::PasswordForm
>();
902 ScopedVector
<autofill::PasswordForm
> forms
;
905 // In current pickles, we expect 64-bit sizes. Failure is an error.
906 success
= DeserializeValueSize(
907 signon_realm
, iter
, version
, false, false, &forms
);
908 UMALogDeserializationStatus(success
);
912 const bool size_32
= sizeof(size_t) == sizeof(uint32_t);
913 if (!DeserializeValueSize(
914 signon_realm
, iter
, version
, size_32
, true, &forms
)) {
915 // We failed to read the pickle using the native architecture of the system.
916 // Try again with the opposite architecture. Note that we do this even on
917 // 32-bit machines, in case we're reading a 64-bit pickle. (Probably rare,
918 // since mostly we expect upgrades, not downgrades, but both are possible.)
919 success
= DeserializeValueSize(
920 signon_realm
, iter
, version
, !size_32
, false, &forms
);
922 UMALogDeserializationStatus(success
);
926 int NativeBackendKWallet::WalletHandle() {
927 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
930 // TODO(mdm): Are we leaking these handles? Find out.
931 int32_t handle
= kInvalidKWalletHandle
;
933 dbus::MethodCall
method_call(kKWalletInterface
, "open");
934 dbus::MessageWriter
builder(&method_call
);
935 builder
.AppendString(wallet_name_
); // wallet
936 builder
.AppendInt64(0); // wid
937 builder
.AppendString(app_name_
); // appid
938 scoped_ptr
<dbus::Response
> response(
939 kwallet_proxy_
->CallMethodAndBlock(
940 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
941 if (!response
.get()) {
942 LOG(ERROR
) << "Error contacting kwalletd (open)";
943 return kInvalidKWalletHandle
;
945 dbus::MessageReader
reader(response
.get());
946 if (!reader
.PopInt32(&handle
)) {
947 LOG(ERROR
) << "Error reading response from kwalletd (open): "
948 << response
->ToString();
949 return kInvalidKWalletHandle
;
951 if (handle
== kInvalidKWalletHandle
) {
952 LOG(ERROR
) << "Error obtaining KWallet handle";
953 return kInvalidKWalletHandle
;
957 // Check if our folder exists.
958 bool has_folder
= false;
960 dbus::MethodCall
method_call(kKWalletInterface
, "hasFolder");
961 dbus::MessageWriter
builder(&method_call
);
962 builder
.AppendInt32(handle
); // handle
963 builder
.AppendString(folder_name_
); // folder
964 builder
.AppendString(app_name_
); // appid
965 scoped_ptr
<dbus::Response
> response(
966 kwallet_proxy_
->CallMethodAndBlock(
967 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
968 if (!response
.get()) {
969 LOG(ERROR
) << "Error contacting kwalletd (hasFolder)";
970 return kInvalidKWalletHandle
;
972 dbus::MessageReader
reader(response
.get());
973 if (!reader
.PopBool(&has_folder
)) {
974 LOG(ERROR
) << "Error reading response from kwalletd (hasFolder): "
975 << response
->ToString();
976 return kInvalidKWalletHandle
;
980 // Create it if it didn't.
982 dbus::MethodCall
method_call(kKWalletInterface
, "createFolder");
983 dbus::MessageWriter
builder(&method_call
);
984 builder
.AppendInt32(handle
); // handle
985 builder
.AppendString(folder_name_
); // folder
986 builder
.AppendString(app_name_
); // appid
987 scoped_ptr
<dbus::Response
> response(
988 kwallet_proxy_
->CallMethodAndBlock(
989 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
990 if (!response
.get()) {
991 LOG(ERROR
) << "Error contacting kwalletd (createFolder)";
992 return kInvalidKWalletHandle
;
994 dbus::MessageReader
reader(response
.get());
995 bool success
= false;
996 if (!reader
.PopBool(&success
)) {
997 LOG(ERROR
) << "Error reading response from kwalletd (createFolder): "
998 << response
->ToString();
999 return kInvalidKWalletHandle
;
1002 LOG(ERROR
) << "Error creating KWallet folder";
1003 return kInvalidKWalletHandle
;
1010 std::string
NativeBackendKWallet::GetProfileSpecificFolderName() const {
1011 // Originally, the folder name was always just "Chrome Form Data".
1012 // Now we use it to distinguish passwords for different profiles.
1013 return base::StringPrintf("%s (%d)", kKWalletFolder
, profile_id_
);