1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/password_manager/native_backend_kwallet_x.h"
10 #include "base/logging.h"
11 #include "base/pickle.h"
12 #include "base/stl_util.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/synchronization/waitable_event.h"
15 #include "base/threading/thread_restrictions.h"
16 #include "components/autofill/core/common/password_form.h"
17 #include "content/public/browser/browser_thread.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
;
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
,
50 // An update check doesn't care about the submit element.
51 if (!update_check
&& a
.submit_element
!= b
.submit_element
)
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
,
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
<< ")";
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
)) {
81 LOG(ERROR
) << "Failed to deserialize URL.";
85 *url
= GURL(url_string
);
91 NativeBackendKWallet::NativeBackendKWallet(LocalProfileId id
,
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;
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
;
148 void NativeBackendKWallet::InitOnDBThread(scoped_refptr
<dbus::Bus
> optional_bus
,
149 base::WaitableEvent
* event
,
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
;
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
);
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
));
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";
199 dbus::MessageReader
reader(response
.get());
201 std::string dbus_name
;
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();
210 if (!error
.empty() || ret
) {
211 LOG(ERROR
) << "Error launching kwalletd: error '" << error
<< "' "
212 << " (code " << ret
<< ")";
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.
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
;
266 bool NativeBackendKWallet::AddLogin(const PasswordForm
& form
) {
267 int wallet_handle
= WalletHandle();
268 if (wallet_handle
== kInvalidKWalletHandle
)
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)) {
287 forms
.push_back(new PasswordForm(form
));
289 bool ok
= SetLoginsList(forms
, form
.signon_realm
, wallet_handle
);
291 STLDeleteElements(&forms
);
295 bool NativeBackendKWallet::UpdateLogin(const PasswordForm
& form
) {
296 int wallet_handle
= WalletHandle();
297 if (wallet_handle
== kInvalidKWalletHandle
)
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))
308 bool ok
= SetLoginsList(forms
, form
.signon_realm
, wallet_handle
);
310 STLDeleteElements(&forms
);
314 bool NativeBackendKWallet::RemoveLogin(const PasswordForm
& form
) {
315 int wallet_handle
= WalletHandle();
316 if (wallet_handle
== kInvalidKWalletHandle
)
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))
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
);
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
)
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)";
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();
367 while (array
.HasMoreData()) {
369 if (!array
.PopString(&realm
)) {
370 LOG(ERROR
) << "Error reading response from kwalletd (entryList): "
371 << response
->ToString();
374 realm_list
.push_back(realm
);
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)";
394 dbus::MessageReader
reader(response
.get());
395 uint8_t* bytes
= NULL
;
397 if (!reader
.PopArrayOfBytes(&bytes
, &length
)) {
398 LOG(ERROR
) << "Error reading response from kwalletd (readEntry): "
399 << response
->ToString();
402 if (!bytes
|| !CheckSerializedValue(bytes
, length
, signon_realm
))
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
)) {
417 kept_forms
.push_back(all_forms
[i
]);
421 if (!SetLoginsList(kept_forms
, signon_realm
, wallet_handle
))
423 STLDeleteElements(&kept_forms
);
428 bool NativeBackendKWallet::GetLogins(const PasswordForm
& form
,
429 PasswordFormList
* forms
) {
430 int wallet_handle
= WalletHandle();
431 if (wallet_handle
== kInvalidKWalletHandle
)
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
)
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
)
449 return GetLoginsList(forms
, true, wallet_handle
);
452 bool NativeBackendKWallet::GetBlacklistLogins(PasswordFormList
* forms
) {
453 int wallet_handle
= WalletHandle();
454 if (wallet_handle
== kInvalidKWalletHandle
)
456 return GetLoginsList(forms
, false, wallet_handle
);
459 bool NativeBackendKWallet::GetLoginsList(PasswordFormList
* forms
,
460 const std::string
& signon_realm
,
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)";
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();
485 // This is not an error. There just isn't a matching entry.
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)";
504 dbus::MessageReader
reader(response
.get());
505 uint8_t* bytes
= NULL
;
507 if (!reader
.PopArrayOfBytes(&bytes
, &length
)) {
508 LOG(ERROR
) << "Error reading response from kwalletd (readEntry): "
509 << response
->ToString();
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).
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
);
531 bool NativeBackendKWallet::GetLoginsList(PasswordFormList
* forms
,
534 PasswordFormList all_forms
;
535 if (!GetAllLogins(&all_forms
, wallet_handle
))
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
]);
550 bool NativeBackendKWallet::GetLoginsList(PasswordFormList
* forms
,
551 const base::Time
& begin
,
552 const base::Time
& end
,
554 PasswordFormList all_forms
;
555 if (!GetAllLogins(&all_forms
, wallet_handle
))
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
]);
572 bool NativeBackendKWallet::GetAllLogins(PasswordFormList
* forms
,
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)";
589 dbus::MessageReader
reader(response
.get());
590 if (!reader
.PopArrayOfStrings(&realm_list
)) {
591 LOG(ERROR
) << "Error reading response from kwalletd (entryList): "
592 << response
->ToString();
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)";
612 dbus::MessageReader
reader(response
.get());
613 uint8_t* bytes
= NULL
;
615 if (!reader
.PopArrayOfBytes(&bytes
, &length
)) {
616 LOG(ERROR
) << "Error reading response from kwalletd (readEntry): "
617 << response
->ToString();
620 if (!bytes
|| !CheckSerializedValue(bytes
, length
, signon_realm
))
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
);
631 bool NativeBackendKWallet::SetLoginsList(const PasswordFormList
& forms
,
632 const std::string
& signon_realm
,
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());
651 if (!reader
.PopInt32(&ret
)) {
652 LOG(ERROR
) << "Error reading response from kwalletd (removeEntry): "
653 << response
->ToString();
657 LOG(ERROR
) << "Bad return code " << ret
<< " from KWallet removeEntry";
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());
681 if (!reader
.PopInt32(&ret
)) {
682 LOG(ERROR
) << "Error reading response from kwalletd (writeEntry): "
683 << response
->ToString();
687 LOG(ERROR
) << "Bad return code " << ret
<< " from KWallet writeEntry";
692 void NativeBackendKWallet::SerializeValue(const PasswordFormList
& forms
,
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());
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
;
723 uint32_t count_32
= 0;
724 if (!iter
.ReadUInt32(&count_32
)) {
725 LOG(ERROR
) << "Failed to deserialize KWallet entry "
726 << "(realm: " << signon_realm
<< ")";
731 if (!iter
.ReadUInt64(&count
)) {
732 LOG(ERROR
) << "Failed to deserialize KWallet entry "
733 << "(realm: " << signon_realm
<< ")";
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.)
746 LOG(ERROR
) << "Suspiciously large number of entries in KWallet entry "
747 << "(" << count
<< "; realm: " << signon_realm
<< ")";
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
);
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
)) {
774 LOG(WARNING
) << "Failed to deserialize version 0 KWallet entry "
775 << "(realm: " << signon_realm
<< ") with native "
776 << "architecture size; will try alternate size.";
778 LOG(ERROR
) << "Failed to deserialize KWallet entry "
779 << "(realm: " << signon_realm
<< ")";
783 form
->scheme
= static_cast<PasswordForm::Scheme
>(scheme
);
784 form
->date_created
= base::Time::FromTimeT(date_created
);
785 forms
->push_back(form
.release());
792 void NativeBackendKWallet::DeserializeValue(const std::string
& signon_realm
,
793 const Pickle
& pickle
,
794 PasswordFormList
* forms
) {
795 PickleIterator
iter(pickle
);
798 if (!iter
.ReadInt(&version
) ||
799 version
< 0 || version
> kPickleVersion
) {
800 LOG(ERROR
) << "Failed to deserialize KWallet entry "
801 << "(realm: " << signon_realm
<< ")";
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
);
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
));
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.
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
;
899 LOG(ERROR
) << "Error creating KWallet folder";
900 return kInvalidKWalletHandle
;
904 // Successful initialization. Try migration if necessary.
906 MigrateToProfileSpecificLogins();
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
)
931 PasswordFormList forms
;
932 if (!GetAllLogins(&forms
, wallet_handle
))
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.
944 for (size_t i
= 0; i
< forms
.size(); ++i
) {
945 if (!AddLogin(*forms
[i
]))
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
)
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_
);
968 // We failed to migrate for some reason. Use the old folder name.
969 folder_name_
= kKWalletFolder
;