Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / password_manager / native_backend_kwallet_x.cc
blob5f7feb487c4f3ee6e6c18ad90ee4205ab8803c94
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/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"
20 #include "dbus/bus.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;
29 namespace {
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 = 7;
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,
54 bool update_check) {
55 // An update check doesn't care about the submit element.
56 if (!update_check && a.submit_element != b.submit_element)
57 return false;
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,
68 size_t length,
69 const std::string& realm) {
70 const base::Pickle::Header* header =
71 reinterpret_cast<const base::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 << ")";
75 return false;
77 return true;
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(base::PickleIterator* iter, bool warn_only, GURL* url) {
83 std::string url_string;
84 if (!iter->ReadString(&url_string)) {
85 if (!warn_only)
86 LOG(ERROR) << "Failed to deserialize URL.";
87 *url = GURL();
88 return false;
90 *url = GURL(url_string);
91 return true;
94 void LogDeserializationWarning(int version,
95 std::string signon_realm,
96 bool warn_only) {
97 if (warn_only) {
98 LOG(WARNING) << "Failed to deserialize version " << version
99 << " KWallet entry (realm: " << signon_realm
100 << ") with native architecture size; will try alternate "
101 << "size.";
102 } else {
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 base::PickleIterator& init_iter,
117 int version,
118 bool size_32,
119 bool warn_only,
120 ScopedVector<autofill::PasswordForm>* forms) {
121 base::PickleIterator iter = init_iter;
123 size_t count = 0;
124 if (size_32) {
125 uint32_t count_32 = 0;
126 if (!iter.ReadUInt32(&count_32)) {
127 LOG(ERROR) << "Failed to deserialize KWallet entry "
128 << "(realm: " << signon_realm << ")";
129 return false;
131 count = count_32;
132 } else {
133 if (!iter.ReadSizeT(&count)) {
134 LOG(ERROR) << "Failed to deserialize KWallet entry "
135 << "(realm: " << signon_realm << ")";
136 return false;
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.)
147 if (!warn_only) {
148 LOG(ERROR) << "Suspiciously large number of entries in KWallet entry "
149 << "(" << count << "; realm: " << signon_realm << ")";
151 return false;
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);
162 int scheme = 0;
163 int64 date_created = 0;
164 int type = 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);
181 return false;
183 form->scheme = static_cast<PasswordForm::Scheme>(scheme);
185 if (version > 1) {
186 if (!iter.ReadInt(&type) ||
187 !iter.ReadInt(&form->times_used) ||
188 !autofill::DeserializeFormData(&iter, &form->form_data)) {
189 LogDeserializationWarning(version, signon_realm, false);
190 return false;
192 form->type = static_cast<PasswordForm::Type>(type);
195 if (version > 2) {
196 int64 date_synced = 0;
197 if (!iter.ReadInt64(&date_synced)) {
198 LogDeserializationWarning(version, signon_realm, false);
199 return false;
201 form->date_synced = base::Time::FromInternalValue(date_synced);
204 if (version > 3) {
205 if (!iter.ReadString16(&form->display_name) ||
206 !ReadGURL(&iter, warn_only, &form->icon_url) ||
207 !ReadGURL(&iter, warn_only, &form->federation_url) ||
208 !iter.ReadBool(&form->skip_zero_click)) {
209 LogDeserializationWarning(version, signon_realm, false);
210 return false;
214 if (version > 4) {
215 form->date_created = base::Time::FromInternalValue(date_created);
216 } else {
217 form->date_created = base::Time::FromTimeT(date_created);
220 if (version > 5) {
221 bool read_success = iter.ReadInt(&generation_upload_status);
222 if (!read_success && version > 6) {
223 // Valid version 6 pickles might still lack the
224 // generation_upload_status, see http://crbug.com/494229#c11.
225 LogDeserializationWarning(version, signon_realm, false);
226 return false;
228 if (read_success) {
229 form->generation_upload_status =
230 static_cast<PasswordForm::GenerationUploadStatus>(
231 generation_upload_status);
235 converted_forms.push_back(form.Pass());
238 forms->swap(converted_forms);
239 return true;
242 // Serializes a list of PasswordForms to be stored in the wallet.
243 void SerializeValue(const std::vector<autofill::PasswordForm*>& forms,
244 base::Pickle* pickle) {
245 pickle->WriteInt(kPickleVersion);
246 pickle->WriteSizeT(forms.size());
247 for (autofill::PasswordForm* form : forms) {
248 pickle->WriteInt(form->scheme);
249 pickle->WriteString(form->origin.spec());
250 pickle->WriteString(form->action.spec());
251 pickle->WriteString16(form->username_element);
252 pickle->WriteString16(form->username_value);
253 pickle->WriteString16(form->password_element);
254 pickle->WriteString16(form->password_value);
255 pickle->WriteString16(form->submit_element);
256 pickle->WriteBool(form->ssl_valid);
257 pickle->WriteBool(form->preferred);
258 pickle->WriteBool(form->blacklisted_by_user);
259 pickle->WriteInt64(form->date_created.ToInternalValue());
260 pickle->WriteInt(form->type);
261 pickle->WriteInt(form->times_used);
262 autofill::SerializeFormData(form->form_data, pickle);
263 pickle->WriteInt64(form->date_synced.ToInternalValue());
264 pickle->WriteString16(form->display_name);
265 pickle->WriteString(form->icon_url.spec());
266 pickle->WriteString(form->federation_url.spec());
267 pickle->WriteBool(form->skip_zero_click);
268 pickle->WriteInt(form->generation_upload_status);
272 // Moves the content of |second| to the end of |first|.
273 void AppendSecondToFirst(ScopedVector<autofill::PasswordForm>* first,
274 ScopedVector<autofill::PasswordForm> second) {
275 first->reserve(first->size() + second.size());
276 first->insert(first->end(), second.begin(), second.end());
277 second.weak_clear();
280 void UMALogDeserializationStatus(bool success) {
281 UMA_HISTOGRAM_BOOLEAN("PasswordManager.KWalletDeserializationStatus",
282 success);
285 } // namespace
287 NativeBackendKWallet::NativeBackendKWallet(LocalProfileId id)
288 : profile_id_(id),
289 kwallet_proxy_(nullptr),
290 app_name_(l10n_util::GetStringUTF8(IDS_PRODUCT_NAME)) {
291 folder_name_ = GetProfileSpecificFolderName();
294 NativeBackendKWallet::~NativeBackendKWallet() {
295 // This destructor is called on the thread that is destroying the Profile
296 // containing the PasswordStore that owns this NativeBackend. Generally that
297 // won't be the DB thread; it will be the UI thread. So we post a message to
298 // shut it down on the DB thread, and it will be destructed afterward when the
299 // scoped_refptr<dbus::Bus> goes out of scope. The NativeBackend will be
300 // destroyed before that occurs, but that's OK.
301 if (session_bus_.get()) {
302 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
303 base::Bind(&dbus::Bus::ShutdownAndBlock,
304 session_bus_.get()));
308 bool NativeBackendKWallet::Init() {
309 // Without the |optional_bus| parameter, a real bus will be instantiated.
310 return InitWithBus(scoped_refptr<dbus::Bus>());
313 bool NativeBackendKWallet::InitWithBus(scoped_refptr<dbus::Bus> optional_bus) {
314 // We must synchronously do a few DBus calls to figure out if initialization
315 // succeeds, but later, we'll want to do most work on the DB thread. So we
316 // have to do the initialization on the DB thread here too, and wait for it.
317 bool success = false;
318 base::WaitableEvent event(false, false);
319 // NativeBackendKWallet isn't reference counted, but we wait for InitWithBus
320 // to finish, so we can safely use base::Unretained here.
321 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
322 base::Bind(&NativeBackendKWallet::InitOnDBThread,
323 base::Unretained(this),
324 optional_bus, &event, &success));
326 // This ScopedAllowWait should not be here. http://crbug.com/125331
327 base::ThreadRestrictions::ScopedAllowWait allow_wait;
328 event.Wait();
329 return success;
332 void NativeBackendKWallet::InitOnDBThread(scoped_refptr<dbus::Bus> optional_bus,
333 base::WaitableEvent* event,
334 bool* success) {
335 DCHECK_CURRENTLY_ON(BrowserThread::DB);
336 DCHECK(!session_bus_.get());
337 if (optional_bus.get()) {
338 // The optional_bus parameter is given when this method is called in tests.
339 session_bus_ = optional_bus;
340 } else {
341 // Get a (real) connection to the session bus.
342 dbus::Bus::Options options;
343 options.bus_type = dbus::Bus::SESSION;
344 options.connection_type = dbus::Bus::PRIVATE;
345 session_bus_ = new dbus::Bus(options);
347 kwallet_proxy_ =
348 session_bus_->GetObjectProxy(kKWalletServiceName,
349 dbus::ObjectPath(kKWalletPath));
350 // kwalletd may not be running. If we get a temporary failure initializing it,
351 // try to start it and then try again. (Note the short-circuit evaluation.)
352 const InitResult result = InitWallet();
353 *success = (result == INIT_SUCCESS ||
354 (result == TEMPORARY_FAIL &&
355 StartKWalletd() && InitWallet() == INIT_SUCCESS));
356 event->Signal();
359 bool NativeBackendKWallet::StartKWalletd() {
360 DCHECK_CURRENTLY_ON(BrowserThread::DB);
361 // Sadly kwalletd doesn't use DBus activation, so we have to make a call to
362 // klauncher to start it.
363 dbus::ObjectProxy* klauncher =
364 session_bus_->GetObjectProxy(kKLauncherServiceName,
365 dbus::ObjectPath(kKLauncherPath));
367 dbus::MethodCall method_call(kKLauncherInterface,
368 "start_service_by_desktop_name");
369 dbus::MessageWriter builder(&method_call);
370 std::vector<std::string> empty;
371 builder.AppendString("kwalletd"); // serviceName
372 builder.AppendArrayOfStrings(empty); // urls
373 builder.AppendArrayOfStrings(empty); // envs
374 builder.AppendString(std::string()); // startup_id
375 builder.AppendBool(false); // blind
376 scoped_ptr<dbus::Response> response(
377 klauncher->CallMethodAndBlock(
378 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
379 if (!response.get()) {
380 LOG(ERROR) << "Error contacting klauncher to start kwalletd";
381 return false;
383 dbus::MessageReader reader(response.get());
384 int32_t ret = -1;
385 std::string dbus_name;
386 std::string error;
387 int32_t pid = -1;
388 if (!reader.PopInt32(&ret) || !reader.PopString(&dbus_name) ||
389 !reader.PopString(&error) || !reader.PopInt32(&pid)) {
390 LOG(ERROR) << "Error reading response from klauncher to start kwalletd: "
391 << response->ToString();
392 return false;
394 if (!error.empty() || ret) {
395 LOG(ERROR) << "Error launching kwalletd: error '" << error << "' "
396 << " (code " << ret << ")";
397 return false;
400 return true;
403 NativeBackendKWallet::InitResult NativeBackendKWallet::InitWallet() {
404 DCHECK_CURRENTLY_ON(BrowserThread::DB);
406 // Check that KWallet is enabled.
407 dbus::MethodCall method_call(kKWalletInterface, "isEnabled");
408 scoped_ptr<dbus::Response> response(
409 kwallet_proxy_->CallMethodAndBlock(
410 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
411 if (!response.get()) {
412 LOG(ERROR) << "Error contacting kwalletd (isEnabled)";
413 return TEMPORARY_FAIL;
415 dbus::MessageReader reader(response.get());
416 bool enabled = false;
417 if (!reader.PopBool(&enabled)) {
418 LOG(ERROR) << "Error reading response from kwalletd (isEnabled): "
419 << response->ToString();
420 return PERMANENT_FAIL;
422 // Not enabled? Don't use KWallet. But also don't warn here.
423 if (!enabled) {
424 VLOG(1) << "kwalletd reports that KWallet is not enabled.";
425 return PERMANENT_FAIL;
430 // Get the wallet name.
431 dbus::MethodCall method_call(kKWalletInterface, "networkWallet");
432 scoped_ptr<dbus::Response> response(
433 kwallet_proxy_->CallMethodAndBlock(
434 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
435 if (!response.get()) {
436 LOG(ERROR) << "Error contacting kwalletd (networkWallet)";
437 return TEMPORARY_FAIL;
439 dbus::MessageReader reader(response.get());
440 if (!reader.PopString(&wallet_name_)) {
441 LOG(ERROR) << "Error reading response from kwalletd (networkWallet): "
442 << response->ToString();
443 return PERMANENT_FAIL;
447 return INIT_SUCCESS;
450 password_manager::PasswordStoreChangeList NativeBackendKWallet::AddLogin(
451 const PasswordForm& form) {
452 int wallet_handle = WalletHandle();
453 if (wallet_handle == kInvalidKWalletHandle)
454 return password_manager::PasswordStoreChangeList();
456 ScopedVector<autofill::PasswordForm> forms;
457 if (!GetLoginsList(form.signon_realm, wallet_handle, &forms))
458 return password_manager::PasswordStoreChangeList();
460 // We search for a login to update, rather than unconditionally appending the
461 // login, because in some cases (especially involving sync) we can be asked to
462 // add a login that already exists. In these cases we want to just update.
463 bool updated = false;
464 password_manager::PasswordStoreChangeList changes;
465 for (size_t i = 0; i < forms.size(); ++i) {
466 // Use the more restrictive removal comparison, so that we never have
467 // duplicate logins that would all be removed together by RemoveLogin().
468 if (CompareForms(form, *forms[i], false)) {
469 changes.push_back(password_manager::PasswordStoreChange(
470 password_manager::PasswordStoreChange::REMOVE, *forms[i]));
471 *forms[i] = form;
472 updated = true;
475 if (!updated)
476 forms.push_back(new PasswordForm(form));
477 changes.push_back(password_manager::PasswordStoreChange(
478 password_manager::PasswordStoreChange::ADD, form));
480 bool ok = SetLoginsList(forms.get(), form.signon_realm, wallet_handle);
481 if (!ok)
482 changes.clear();
484 return changes;
487 bool NativeBackendKWallet::UpdateLogin(
488 const PasswordForm& form,
489 password_manager::PasswordStoreChangeList* changes) {
490 DCHECK(changes);
491 changes->clear();
492 int wallet_handle = WalletHandle();
493 if (wallet_handle == kInvalidKWalletHandle)
494 return false;
496 ScopedVector<autofill::PasswordForm> forms;
497 if (!GetLoginsList(form.signon_realm, wallet_handle, &forms))
498 return false;
500 bool updated = false;
501 for (size_t i = 0; i < forms.size(); ++i) {
502 if (CompareForms(form, *forms[i], true)) {
503 *forms[i] = form;
504 updated = true;
507 if (!updated)
508 return true;
510 if (SetLoginsList(forms.get(), form.signon_realm, wallet_handle)) {
511 changes->push_back(password_manager::PasswordStoreChange(
512 password_manager::PasswordStoreChange::UPDATE, form));
513 return true;
516 return false;
519 bool NativeBackendKWallet::RemoveLogin(const PasswordForm& form) {
520 int wallet_handle = WalletHandle();
521 if (wallet_handle == kInvalidKWalletHandle)
522 return false;
524 ScopedVector<autofill::PasswordForm> all_forms;
525 if (!GetLoginsList(form.signon_realm, wallet_handle, &all_forms))
526 return false;
528 ScopedVector<autofill::PasswordForm> kept_forms;
529 kept_forms.reserve(all_forms.size());
530 for (auto& saved_form : all_forms) {
531 if (!CompareForms(form, *saved_form, false)) {
532 kept_forms.push_back(saved_form);
533 saved_form = nullptr;
537 // Update the entry in the wallet, possibly deleting it.
538 return SetLoginsList(kept_forms.get(), form.signon_realm, wallet_handle);
541 bool NativeBackendKWallet::RemoveLoginsCreatedBetween(
542 base::Time delete_begin,
543 base::Time delete_end,
544 password_manager::PasswordStoreChangeList* changes) {
545 return RemoveLoginsBetween(
546 delete_begin, delete_end, CREATION_TIMESTAMP, changes);
549 bool NativeBackendKWallet::RemoveLoginsSyncedBetween(
550 base::Time delete_begin,
551 base::Time delete_end,
552 password_manager::PasswordStoreChangeList* changes) {
553 return RemoveLoginsBetween(delete_begin, delete_end, SYNC_TIMESTAMP, changes);
556 bool NativeBackendKWallet::GetLogins(
557 const PasswordForm& form,
558 ScopedVector<autofill::PasswordForm>* forms) {
559 int wallet_handle = WalletHandle();
560 if (wallet_handle == kInvalidKWalletHandle)
561 return false;
562 return GetLoginsList(form.signon_realm, wallet_handle, forms);
565 bool NativeBackendKWallet::GetAutofillableLogins(
566 ScopedVector<autofill::PasswordForm>* forms) {
567 int wallet_handle = WalletHandle();
568 if (wallet_handle == kInvalidKWalletHandle)
569 return false;
570 return GetLoginsList(BlacklistOptions::AUTOFILLABLE, wallet_handle, forms);
573 bool NativeBackendKWallet::GetBlacklistLogins(
574 ScopedVector<autofill::PasswordForm>* forms) {
575 int wallet_handle = WalletHandle();
576 if (wallet_handle == kInvalidKWalletHandle)
577 return false;
578 return GetLoginsList(BlacklistOptions::BLACKLISTED, wallet_handle, forms);
581 bool NativeBackendKWallet::GetLoginsList(
582 const std::string& signon_realm,
583 int wallet_handle,
584 ScopedVector<autofill::PasswordForm>* forms) {
585 forms->clear();
586 // Is there an entry in the wallet?
588 dbus::MethodCall method_call(kKWalletInterface, "hasEntry");
589 dbus::MessageWriter builder(&method_call);
590 builder.AppendInt32(wallet_handle); // handle
591 builder.AppendString(folder_name_); // folder
592 builder.AppendString(signon_realm); // key
593 builder.AppendString(app_name_); // appid
594 scoped_ptr<dbus::Response> response(
595 kwallet_proxy_->CallMethodAndBlock(
596 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
597 if (!response.get()) {
598 LOG(ERROR) << "Error contacting kwalletd (hasEntry)";
599 return false;
601 dbus::MessageReader reader(response.get());
602 bool has_entry = false;
603 if (!reader.PopBool(&has_entry)) {
604 LOG(ERROR) << "Error reading response from kwalletd (hasEntry): "
605 << response->ToString();
606 return false;
608 if (!has_entry) {
609 // This is not an error. There just isn't a matching entry.
610 return true;
615 dbus::MethodCall method_call(kKWalletInterface, "readEntry");
616 dbus::MessageWriter builder(&method_call);
617 builder.AppendInt32(wallet_handle); // handle
618 builder.AppendString(folder_name_); // folder
619 builder.AppendString(signon_realm); // key
620 builder.AppendString(app_name_); // appid
621 scoped_ptr<dbus::Response> response(
622 kwallet_proxy_->CallMethodAndBlock(
623 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
624 if (!response.get()) {
625 LOG(ERROR) << "Error contacting kwalletd (readEntry)";
626 return false;
628 dbus::MessageReader reader(response.get());
629 const uint8_t* bytes = nullptr;
630 size_t length = 0;
631 if (!reader.PopArrayOfBytes(&bytes, &length)) {
632 LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
633 << response->ToString();
634 return false;
636 if (!bytes)
637 return false;
638 if (!CheckSerializedValue(bytes, length, signon_realm)) {
639 // This is weird, but we choose not to call it an error. There is an
640 // invalid entry somehow, but by just ignoring it, we make it easier to
641 // repair without having to delete it using kwalletmanager (that is, by
642 // just saving a new password within this realm to overwrite it).
643 return true;
646 // Can't we all just agree on whether bytes are signed or not? Please?
647 base::Pickle pickle(reinterpret_cast<const char*>(bytes), length);
648 *forms = DeserializeValue(signon_realm, pickle);
651 return true;
654 bool NativeBackendKWallet::GetLoginsList(
655 BlacklistOptions options,
656 int wallet_handle,
657 ScopedVector<autofill::PasswordForm>* forms) {
658 forms->clear();
659 ScopedVector<autofill::PasswordForm> all_forms;
660 if (!GetAllLogins(wallet_handle, &all_forms))
661 return false;
663 // We have to read all the entries, and then filter them here.
664 forms->reserve(all_forms.size());
665 for (auto& saved_form : all_forms) {
666 if (saved_form->blacklisted_by_user ==
667 (options == BlacklistOptions::BLACKLISTED)) {
668 forms->push_back(saved_form);
669 saved_form = nullptr;
673 return true;
676 bool NativeBackendKWallet::GetAllLogins(
677 int wallet_handle,
678 ScopedVector<autofill::PasswordForm>* forms) {
679 // We could probably also use readEntryList here.
680 std::vector<std::string> realm_list;
682 dbus::MethodCall method_call(kKWalletInterface, "entryList");
683 dbus::MessageWriter builder(&method_call);
684 builder.AppendInt32(wallet_handle); // handle
685 builder.AppendString(folder_name_); // folder
686 builder.AppendString(app_name_); // appid
687 scoped_ptr<dbus::Response> response(
688 kwallet_proxy_->CallMethodAndBlock(
689 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
690 if (!response.get()) {
691 LOG(ERROR) << "Error contacting kwalletd (entryList)";
692 return false;
694 dbus::MessageReader reader(response.get());
695 if (!reader.PopArrayOfStrings(&realm_list)) {
696 LOG(ERROR) << "Error reading response from kwalletd (entryList): "
697 << response->ToString();
698 return false;
702 forms->clear();
703 for (const std::string& signon_realm : realm_list) {
704 dbus::MethodCall method_call(kKWalletInterface, "readEntry");
705 dbus::MessageWriter builder(&method_call);
706 builder.AppendInt32(wallet_handle); // handle
707 builder.AppendString(folder_name_); // folder
708 builder.AppendString(signon_realm); // key
709 builder.AppendString(app_name_); // appid
710 scoped_ptr<dbus::Response> response(
711 kwallet_proxy_->CallMethodAndBlock(
712 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
713 if (!response.get()) {
714 LOG(ERROR) << "Error contacting kwalletd (readEntry)";
715 return false;
717 dbus::MessageReader reader(response.get());
718 const uint8_t* bytes = nullptr;
719 size_t length = 0;
720 if (!reader.PopArrayOfBytes(&bytes, &length)) {
721 LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
722 << response->ToString();
723 return false;
725 if (!bytes || !CheckSerializedValue(bytes, length, signon_realm))
726 continue;
728 // Can't we all just agree on whether bytes are signed or not? Please?
729 base::Pickle pickle(reinterpret_cast<const char*>(bytes), length);
730 AppendSecondToFirst(forms, DeserializeValue(signon_realm, pickle));
732 return true;
735 bool NativeBackendKWallet::SetLoginsList(
736 const std::vector<autofill::PasswordForm*>& forms,
737 const std::string& signon_realm,
738 int wallet_handle) {
739 if (forms.empty()) {
740 // No items left? Remove the entry from the wallet.
741 dbus::MethodCall method_call(kKWalletInterface, "removeEntry");
742 dbus::MessageWriter builder(&method_call);
743 builder.AppendInt32(wallet_handle); // handle
744 builder.AppendString(folder_name_); // folder
745 builder.AppendString(signon_realm); // key
746 builder.AppendString(app_name_); // appid
747 scoped_ptr<dbus::Response> response(
748 kwallet_proxy_->CallMethodAndBlock(
749 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
750 if (!response.get()) {
751 LOG(ERROR) << "Error contacting kwalletd (removeEntry)";
752 return kInvalidKWalletHandle;
754 dbus::MessageReader reader(response.get());
755 int ret = 0;
756 if (!reader.PopInt32(&ret)) {
757 LOG(ERROR) << "Error reading response from kwalletd (removeEntry): "
758 << response->ToString();
759 return false;
761 if (ret != 0)
762 LOG(ERROR) << "Bad return code " << ret << " from KWallet removeEntry";
763 return ret == 0;
766 base::Pickle value;
767 SerializeValue(forms, &value);
769 dbus::MethodCall method_call(kKWalletInterface, "writeEntry");
770 dbus::MessageWriter builder(&method_call);
771 builder.AppendInt32(wallet_handle); // handle
772 builder.AppendString(folder_name_); // folder
773 builder.AppendString(signon_realm); // key
774 builder.AppendArrayOfBytes(static_cast<const uint8_t*>(value.data()),
775 value.size()); // value
776 builder.AppendString(app_name_); // appid
777 scoped_ptr<dbus::Response> response(
778 kwallet_proxy_->CallMethodAndBlock(
779 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
780 if (!response.get()) {
781 LOG(ERROR) << "Error contacting kwalletd (writeEntry)";
782 return kInvalidKWalletHandle;
784 dbus::MessageReader reader(response.get());
785 int ret = 0;
786 if (!reader.PopInt32(&ret)) {
787 LOG(ERROR) << "Error reading response from kwalletd (writeEntry): "
788 << response->ToString();
789 return false;
791 if (ret != 0)
792 LOG(ERROR) << "Bad return code " << ret << " from KWallet writeEntry";
793 return ret == 0;
796 bool NativeBackendKWallet::RemoveLoginsBetween(
797 base::Time delete_begin,
798 base::Time delete_end,
799 TimestampToCompare date_to_compare,
800 password_manager::PasswordStoreChangeList* changes) {
801 DCHECK(changes);
802 changes->clear();
803 int wallet_handle = WalletHandle();
804 if (wallet_handle == kInvalidKWalletHandle)
805 return false;
807 // We could probably also use readEntryList here.
808 std::vector<std::string> realm_list;
810 dbus::MethodCall method_call(kKWalletInterface, "entryList");
811 dbus::MessageWriter builder(&method_call);
812 builder.AppendInt32(wallet_handle); // handle
813 builder.AppendString(folder_name_); // folder
814 builder.AppendString(app_name_); // appid
815 scoped_ptr<dbus::Response> response(kwallet_proxy_->CallMethodAndBlock(
816 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
817 if (!response.get()) {
818 LOG(ERROR) << "Error contacting kwalletd (entryList)";
819 return false;
821 dbus::MessageReader reader(response.get());
822 dbus::MessageReader array(response.get());
823 if (!reader.PopArray(&array)) {
824 LOG(ERROR) << "Error reading response from kwalletd (entryList): "
825 << response->ToString();
826 return false;
828 while (array.HasMoreData()) {
829 std::string realm;
830 if (!array.PopString(&realm)) {
831 LOG(ERROR) << "Error reading response from kwalletd (entryList): "
832 << response->ToString();
833 return false;
835 realm_list.push_back(realm);
839 bool ok = true;
840 for (size_t i = 0; i < realm_list.size(); ++i) {
841 const std::string& signon_realm = realm_list[i];
842 dbus::MethodCall method_call(kKWalletInterface, "readEntry");
843 dbus::MessageWriter builder(&method_call);
844 builder.AppendInt32(wallet_handle); // handle
845 builder.AppendString(folder_name_); // folder
846 builder.AppendString(signon_realm); // key
847 builder.AppendString(app_name_); // appid
848 scoped_ptr<dbus::Response> response(kwallet_proxy_->CallMethodAndBlock(
849 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
850 if (!response.get()) {
851 LOG(ERROR) << "Error contacting kwalletd (readEntry)";
852 continue;
854 dbus::MessageReader reader(response.get());
855 const uint8_t* bytes = nullptr;
856 size_t length = 0;
857 if (!reader.PopArrayOfBytes(&bytes, &length)) {
858 LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
859 << response->ToString();
860 continue;
862 if (!bytes || !CheckSerializedValue(bytes, length, signon_realm))
863 continue;
865 // Can't we all just agree on whether bytes are signed or not? Please?
866 base::Pickle pickle(reinterpret_cast<const char*>(bytes), length);
867 ScopedVector<autofill::PasswordForm> all_forms =
868 DeserializeValue(signon_realm, pickle);
870 ScopedVector<autofill::PasswordForm> kept_forms;
871 kept_forms.reserve(all_forms.size());
872 base::Time autofill::PasswordForm::*date_member =
873 date_to_compare == CREATION_TIMESTAMP
874 ? &autofill::PasswordForm::date_created
875 : &autofill::PasswordForm::date_synced;
876 for (auto& saved_form : all_forms) {
877 if (delete_begin <= saved_form->*date_member &&
878 (delete_end.is_null() || saved_form->*date_member < delete_end)) {
879 changes->push_back(password_manager::PasswordStoreChange(
880 password_manager::PasswordStoreChange::REMOVE, *saved_form));
881 } else {
882 kept_forms.push_back(saved_form);
883 saved_form = nullptr;
887 if (!SetLoginsList(kept_forms.get(), signon_realm, wallet_handle)) {
888 ok = false;
889 changes->clear();
892 return ok;
895 // static
896 ScopedVector<autofill::PasswordForm> NativeBackendKWallet::DeserializeValue(
897 const std::string& signon_realm,
898 const base::Pickle& pickle) {
899 base::PickleIterator iter(pickle);
901 int version = -1;
902 if (!iter.ReadInt(&version) ||
903 version < 0 || version > kPickleVersion) {
904 LOG(ERROR) << "Failed to deserialize KWallet entry "
905 << "(realm: " << signon_realm << ")";
906 return ScopedVector<autofill::PasswordForm>();
909 ScopedVector<autofill::PasswordForm> forms;
910 bool success = true;
911 if (version > 0) {
912 // In current pickles, we expect 64-bit sizes. Failure is an error.
913 success = DeserializeValueSize(
914 signon_realm, iter, version, false, false, &forms);
915 UMALogDeserializationStatus(success);
916 return forms.Pass();
919 const bool size_32 = sizeof(size_t) == sizeof(uint32_t);
920 if (!DeserializeValueSize(
921 signon_realm, iter, version, size_32, true, &forms)) {
922 // We failed to read the pickle using the native architecture of the system.
923 // Try again with the opposite architecture. Note that we do this even on
924 // 32-bit machines, in case we're reading a 64-bit pickle. (Probably rare,
925 // since mostly we expect upgrades, not downgrades, but both are possible.)
926 success = DeserializeValueSize(
927 signon_realm, iter, version, !size_32, false, &forms);
929 UMALogDeserializationStatus(success);
930 return forms.Pass();
933 int NativeBackendKWallet::WalletHandle() {
934 DCHECK_CURRENTLY_ON(BrowserThread::DB);
936 // Open the wallet.
937 // TODO(mdm): Are we leaking these handles? Find out.
938 int32_t handle = kInvalidKWalletHandle;
940 dbus::MethodCall method_call(kKWalletInterface, "open");
941 dbus::MessageWriter builder(&method_call);
942 builder.AppendString(wallet_name_); // wallet
943 builder.AppendInt64(0); // wid
944 builder.AppendString(app_name_); // appid
945 scoped_ptr<dbus::Response> response(
946 kwallet_proxy_->CallMethodAndBlock(
947 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
948 if (!response.get()) {
949 LOG(ERROR) << "Error contacting kwalletd (open)";
950 return kInvalidKWalletHandle;
952 dbus::MessageReader reader(response.get());
953 if (!reader.PopInt32(&handle)) {
954 LOG(ERROR) << "Error reading response from kwalletd (open): "
955 << response->ToString();
956 return kInvalidKWalletHandle;
958 if (handle == kInvalidKWalletHandle) {
959 LOG(ERROR) << "Error obtaining KWallet handle";
960 return kInvalidKWalletHandle;
964 // Check if our folder exists.
965 bool has_folder = false;
967 dbus::MethodCall method_call(kKWalletInterface, "hasFolder");
968 dbus::MessageWriter builder(&method_call);
969 builder.AppendInt32(handle); // handle
970 builder.AppendString(folder_name_); // folder
971 builder.AppendString(app_name_); // appid
972 scoped_ptr<dbus::Response> response(
973 kwallet_proxy_->CallMethodAndBlock(
974 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
975 if (!response.get()) {
976 LOG(ERROR) << "Error contacting kwalletd (hasFolder)";
977 return kInvalidKWalletHandle;
979 dbus::MessageReader reader(response.get());
980 if (!reader.PopBool(&has_folder)) {
981 LOG(ERROR) << "Error reading response from kwalletd (hasFolder): "
982 << response->ToString();
983 return kInvalidKWalletHandle;
987 // Create it if it didn't.
988 if (!has_folder) {
989 dbus::MethodCall method_call(kKWalletInterface, "createFolder");
990 dbus::MessageWriter builder(&method_call);
991 builder.AppendInt32(handle); // handle
992 builder.AppendString(folder_name_); // folder
993 builder.AppendString(app_name_); // appid
994 scoped_ptr<dbus::Response> response(
995 kwallet_proxy_->CallMethodAndBlock(
996 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
997 if (!response.get()) {
998 LOG(ERROR) << "Error contacting kwalletd (createFolder)";
999 return kInvalidKWalletHandle;
1001 dbus::MessageReader reader(response.get());
1002 bool success = false;
1003 if (!reader.PopBool(&success)) {
1004 LOG(ERROR) << "Error reading response from kwalletd (createFolder): "
1005 << response->ToString();
1006 return kInvalidKWalletHandle;
1008 if (!success) {
1009 LOG(ERROR) << "Error creating KWallet folder";
1010 return kInvalidKWalletHandle;
1014 return handle;
1017 std::string NativeBackendKWallet::GetProfileSpecificFolderName() const {
1018 // Originally, the folder name was always just "Chrome Form Data".
1019 // Now we use it to distinguish passwords for different profiles.
1020 return base::StringPrintf("%s (%d)", kKWalletFolder, profile_id_);