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.
4
5#include "chrome/browser/password_manager/native_backend_kwallet_x.h"
6
7#include <vector>
8
9#include "base/bind.h"
10#include "base/logging.h"
11#include "base/pickle.h"
12#include "base/stl_util.h"
13#include "base/strings/stringprintf.h"
14#include "base/synchronization/waitable_event.h"
15#include "base/threading/thread_restrictions.h"
16#include "chrome/grit/chromium_strings.h"
17#include "components/autofill/core/common/password_form.h"
18#include "content/public/browser/browser_thread.h"
19#include "dbus/bus.h"
20#include "dbus/message.h"
21#include "dbus/object_path.h"
22#include "dbus/object_proxy.h"
23#include "ui/base/l10n/l10n_util.h"
24
25using autofill::PasswordForm;
26using content::BrowserThread;
27
28namespace {
29
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.
33const char kKWalletFolder[] = "Chrome Form Data";
34
35// DBus service, path, and interface names for klauncher and kwalletd.
36const char kKWalletServiceName[] = "org.kde.kwalletd";
37const char kKWalletPath[] = "/modules/kwalletd";
38const char kKWalletInterface[] = "org.kde.KWallet";
39const char kKLauncherServiceName[] = "org.kde.klauncher";
40const char kKLauncherPath[] = "/KLauncher";
41const char kKLauncherInterface[] = "org.kde.KLauncher";
42
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.
47bool CompareForms(const autofill::PasswordForm& a,
48                  const autofill::PasswordForm& b,
49                  bool update_check) {
50  // An update check doesn't care about the submit element.
51  if (!update_check && a.submit_element != b.submit_element)
52    return false;
53  return a.origin           == b.origin &&
54         a.password_element == b.password_element &&
55         a.signon_realm     == b.signon_realm &&
56         a.username_element == b.username_element &&
57         a.username_value   == b.username_value;
58}
59
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.
62bool CheckSerializedValue(const uint8_t* byte_array,
63                          size_t length,
64                          const std::string& realm) {
65  const Pickle::Header* header =
66      reinterpret_cast<const Pickle::Header*>(byte_array);
67  if (length < sizeof(*header) ||
68      header->payload_size > length - sizeof(*header)) {
69    LOG(WARNING) << "Invalid KWallet entry detected (realm: " << realm << ")";
70    return false;
71  }
72  return true;
73}
74
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.
77bool ReadGURL(PickleIterator* iter, bool warn_only, GURL* url) {
78  std::string url_string;
79  if (!iter->ReadString(&url_string)) {
80    if (!warn_only)
81      LOG(ERROR) << "Failed to deserialize URL.";
82    *url = GURL();
83    return false;
84  }
85  *url = GURL(url_string);
86  return true;
87}
88
89void LogDeserializationWarning(int version,
90                               std::string signon_realm,
91                               bool warn_only) {
92  if (warn_only) {
93    LOG(WARNING) << "Failed to deserialize version " << version
94                 << " KWallet entry (realm: " << signon_realm
95                 << ") with native architecture size; will try alternate "
96                 << "size.";
97  } else {
98    LOG(ERROR) << "Failed to deserialize version " << version
99               << " KWallet entry (realm: " << signon_realm << ")";
100  }
101}
102
103}  // namespace
104
105NativeBackendKWallet::NativeBackendKWallet(LocalProfileId id)
106    : profile_id_(id),
107      kwallet_proxy_(NULL),
108      app_name_(l10n_util::GetStringUTF8(IDS_PRODUCT_NAME)) {
109  folder_name_ = GetProfileSpecificFolderName();
110}
111
112NativeBackendKWallet::~NativeBackendKWallet() {
113  // This destructor is called on the thread that is destroying the Profile
114  // containing the PasswordStore that owns this NativeBackend. Generally that
115  // won't be the DB thread; it will be the UI thread. So we post a message to
116  // shut it down on the DB thread, and it will be destructed afterward when the
117  // scoped_refptr<dbus::Bus> goes out of scope. The NativeBackend will be
118  // destroyed before that occurs, but that's OK.
119  if (session_bus_.get()) {
120    BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
121                            base::Bind(&dbus::Bus::ShutdownAndBlock,
122                                       session_bus_.get()));
123  }
124}
125
126bool NativeBackendKWallet::Init() {
127  // Without the |optional_bus| parameter, a real bus will be instantiated.
128  return InitWithBus(scoped_refptr<dbus::Bus>());
129}
130
131bool NativeBackendKWallet::InitWithBus(scoped_refptr<dbus::Bus> optional_bus) {
132  // We must synchronously do a few DBus calls to figure out if initialization
133  // succeeds, but later, we'll want to do most work on the DB thread. So we
134  // have to do the initialization on the DB thread here too, and wait for it.
135  bool success = false;
136  base::WaitableEvent event(false, false);
137  // NativeBackendKWallet isn't reference counted, but we wait for InitWithBus
138  // to finish, so we can safely use base::Unretained here.
139  BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
140                          base::Bind(&NativeBackendKWallet::InitOnDBThread,
141                                     base::Unretained(this),
142                                     optional_bus, &event, &success));
143
144  // This ScopedAllowWait should not be here. http://crbug.com/125331
145  base::ThreadRestrictions::ScopedAllowWait allow_wait;
146  event.Wait();
147  return success;
148}
149
150void NativeBackendKWallet::InitOnDBThread(scoped_refptr<dbus::Bus> optional_bus,
151                                          base::WaitableEvent* event,
152                                          bool* success) {
153  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
154  DCHECK(!session_bus_.get());
155  if (optional_bus.get()) {
156    // The optional_bus parameter is given when this method is called in tests.
157    session_bus_ = optional_bus;
158  } else {
159    // Get a (real) connection to the session bus.
160    dbus::Bus::Options options;
161    options.bus_type = dbus::Bus::SESSION;
162    options.connection_type = dbus::Bus::PRIVATE;
163    session_bus_ = new dbus::Bus(options);
164  }
165  kwallet_proxy_ =
166      session_bus_->GetObjectProxy(kKWalletServiceName,
167                                   dbus::ObjectPath(kKWalletPath));
168  // kwalletd may not be running. If we get a temporary failure initializing it,
169  // try to start it and then try again. (Note the short-circuit evaluation.)
170  const InitResult result = InitWallet();
171  *success = (result == INIT_SUCCESS ||
172              (result == TEMPORARY_FAIL &&
173               StartKWalletd() && InitWallet() == INIT_SUCCESS));
174  event->Signal();
175}
176
177bool NativeBackendKWallet::StartKWalletd() {
178  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
179  // Sadly kwalletd doesn't use DBus activation, so we have to make a call to
180  // klauncher to start it.
181  dbus::ObjectProxy* klauncher =
182      session_bus_->GetObjectProxy(kKLauncherServiceName,
183                                   dbus::ObjectPath(kKLauncherPath));
184
185  dbus::MethodCall method_call(kKLauncherInterface,
186                               "start_service_by_desktop_name");
187  dbus::MessageWriter builder(&method_call);
188  std::vector<std::string> empty;
189  builder.AppendString("kwalletd");     // serviceName
190  builder.AppendArrayOfStrings(empty);  // urls
191  builder.AppendArrayOfStrings(empty);  // envs
192  builder.AppendString(std::string());  // startup_id
193  builder.AppendBool(false);            // blind
194  scoped_ptr<dbus::Response> response(
195      klauncher->CallMethodAndBlock(
196          &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
197  if (!response.get()) {
198    LOG(ERROR) << "Error contacting klauncher to start kwalletd";
199    return false;
200  }
201  dbus::MessageReader reader(response.get());
202  int32_t ret = -1;
203  std::string dbus_name;
204  std::string error;
205  int32_t pid = -1;
206  if (!reader.PopInt32(&ret) || !reader.PopString(&dbus_name) ||
207      !reader.PopString(&error) || !reader.PopInt32(&pid)) {
208    LOG(ERROR) << "Error reading response from klauncher to start kwalletd: "
209               << response->ToString();
210    return false;
211  }
212  if (!error.empty() || ret) {
213    LOG(ERROR) << "Error launching kwalletd: error '" << error << "' "
214               << " (code " << ret << ")";
215    return false;
216  }
217
218  return true;
219}
220
221NativeBackendKWallet::InitResult NativeBackendKWallet::InitWallet() {
222  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
223  {
224    // Check that KWallet is enabled.
225    dbus::MethodCall method_call(kKWalletInterface, "isEnabled");
226    scoped_ptr<dbus::Response> response(
227        kwallet_proxy_->CallMethodAndBlock(
228            &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
229    if (!response.get()) {
230      LOG(ERROR) << "Error contacting kwalletd (isEnabled)";
231      return TEMPORARY_FAIL;
232    }
233    dbus::MessageReader reader(response.get());
234    bool enabled = false;
235    if (!reader.PopBool(&enabled)) {
236      LOG(ERROR) << "Error reading response from kwalletd (isEnabled): "
237                 << response->ToString();
238      return PERMANENT_FAIL;
239    }
240    // Not enabled? Don't use KWallet. But also don't warn here.
241    if (!enabled) {
242      VLOG(1) << "kwalletd reports that KWallet is not enabled.";
243      return PERMANENT_FAIL;
244    }
245  }
246
247  {
248    // Get the wallet name.
249    dbus::MethodCall method_call(kKWalletInterface, "networkWallet");
250    scoped_ptr<dbus::Response> response(
251        kwallet_proxy_->CallMethodAndBlock(
252            &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
253    if (!response.get()) {
254      LOG(ERROR) << "Error contacting kwalletd (networkWallet)";
255      return TEMPORARY_FAIL;
256    }
257    dbus::MessageReader reader(response.get());
258    if (!reader.PopString(&wallet_name_)) {
259      LOG(ERROR) << "Error reading response from kwalletd (networkWallet): "
260                 << response->ToString();
261      return PERMANENT_FAIL;
262    }
263  }
264
265  return INIT_SUCCESS;
266}
267
268password_manager::PasswordStoreChangeList NativeBackendKWallet::AddLogin(
269    const PasswordForm& form) {
270  int wallet_handle = WalletHandle();
271  if (wallet_handle == kInvalidKWalletHandle)
272    return password_manager::PasswordStoreChangeList();
273
274  ScopedVector<autofill::PasswordForm> forms;
275  GetLoginsList(&forms.get(), form.signon_realm, wallet_handle);
276
277  // We search for a login to update, rather than unconditionally appending the
278  // login, because in some cases (especially involving sync) we can be asked to
279  // add a login that already exists. In these cases we want to just update.
280  bool updated = false;
281  password_manager::PasswordStoreChangeList changes;
282  for (size_t i = 0; i < forms.size(); ++i) {
283    // Use the more restrictive removal comparison, so that we never have
284    // duplicate logins that would all be removed together by RemoveLogin().
285    if (CompareForms(form, *forms[i], false)) {
286      changes.push_back(password_manager::PasswordStoreChange(
287          password_manager::PasswordStoreChange::REMOVE, *forms[i]));
288      *forms[i] = form;
289      updated = true;
290    }
291  }
292  if (!updated)
293    forms.push_back(new PasswordForm(form));
294  changes.push_back(password_manager::PasswordStoreChange(
295      password_manager::PasswordStoreChange::ADD, form));
296
297  bool ok = SetLoginsList(forms.get(), form.signon_realm, wallet_handle);
298  if (!ok)
299    changes.clear();
300
301  return changes;
302}
303
304bool NativeBackendKWallet::UpdateLogin(
305    const PasswordForm& form,
306    password_manager::PasswordStoreChangeList* changes) {
307  DCHECK(changes);
308  changes->clear();
309  int wallet_handle = WalletHandle();
310  if (wallet_handle == kInvalidKWalletHandle)
311    return false;
312
313  ScopedVector<autofill::PasswordForm> forms;
314  GetLoginsList(&forms.get(), form.signon_realm, wallet_handle);
315
316  bool updated = false;
317  for (size_t i = 0; i < forms.size(); ++i) {
318    if (CompareForms(form, *forms[i], true)) {
319      *forms[i] = form;
320      updated = true;
321    }
322  }
323  if (!updated)
324    return true;
325
326  if (SetLoginsList(forms.get(), form.signon_realm, wallet_handle)) {
327    changes->push_back(password_manager::PasswordStoreChange(
328        password_manager::PasswordStoreChange::UPDATE, form));
329    return true;
330  }
331
332  return false;
333}
334
335bool NativeBackendKWallet::RemoveLogin(const PasswordForm& form) {
336  int wallet_handle = WalletHandle();
337  if (wallet_handle == kInvalidKWalletHandle)
338    return false;
339
340  PasswordFormList all_forms;
341  GetLoginsList(&all_forms, form.signon_realm, wallet_handle);
342
343  PasswordFormList kept_forms;
344  kept_forms.reserve(all_forms.size());
345  for (size_t i = 0; i < all_forms.size(); ++i) {
346    if (CompareForms(form, *all_forms[i], false))
347      delete all_forms[i];
348    else
349      kept_forms.push_back(all_forms[i]);
350  }
351
352  // Update the entry in the wallet, possibly deleting it.
353  bool ok = SetLoginsList(kept_forms, form.signon_realm, wallet_handle);
354
355  STLDeleteElements(&kept_forms);
356  return ok;
357}
358
359bool NativeBackendKWallet::RemoveLoginsCreatedBetween(
360    base::Time delete_begin,
361    base::Time delete_end,
362    password_manager::PasswordStoreChangeList* changes) {
363  return RemoveLoginsBetween(
364      delete_begin, delete_end, CREATION_TIMESTAMP, changes);
365}
366
367bool NativeBackendKWallet::RemoveLoginsSyncedBetween(
368    base::Time delete_begin,
369    base::Time delete_end,
370    password_manager::PasswordStoreChangeList* changes) {
371  return RemoveLoginsBetween(delete_begin, delete_end, SYNC_TIMESTAMP, changes);
372}
373
374bool NativeBackendKWallet::GetLogins(const PasswordForm& form,
375                                     PasswordFormList* forms) {
376  int wallet_handle = WalletHandle();
377  if (wallet_handle == kInvalidKWalletHandle)
378    return false;
379  return GetLoginsList(forms, form.signon_realm, wallet_handle);
380}
381
382bool NativeBackendKWallet::GetAutofillableLogins(PasswordFormList* forms) {
383  int wallet_handle = WalletHandle();
384  if (wallet_handle == kInvalidKWalletHandle)
385    return false;
386  return GetLoginsList(forms, true, wallet_handle);
387}
388
389bool NativeBackendKWallet::GetBlacklistLogins(PasswordFormList* forms) {
390  int wallet_handle = WalletHandle();
391  if (wallet_handle == kInvalidKWalletHandle)
392    return false;
393  return GetLoginsList(forms, false, wallet_handle);
394}
395
396bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms,
397                                         const std::string& signon_realm,
398                                         int wallet_handle) {
399  // Is there an entry in the wallet?
400  {
401    dbus::MethodCall method_call(kKWalletInterface, "hasEntry");
402    dbus::MessageWriter builder(&method_call);
403    builder.AppendInt32(wallet_handle);  // handle
404    builder.AppendString(folder_name_);  // folder
405    builder.AppendString(signon_realm);  // key
406    builder.AppendString(app_name_);     // appid
407    scoped_ptr<dbus::Response> response(
408        kwallet_proxy_->CallMethodAndBlock(
409            &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
410    if (!response.get()) {
411      LOG(ERROR) << "Error contacting kwalletd (hasEntry)";
412      return false;
413    }
414    dbus::MessageReader reader(response.get());
415    bool has_entry = false;
416    if (!reader.PopBool(&has_entry)) {
417      LOG(ERROR) << "Error reading response from kwalletd (hasEntry): "
418                 << response->ToString();
419      return false;
420    }
421    if (!has_entry) {
422      // This is not an error. There just isn't a matching entry.
423      return true;
424    }
425  }
426
427  {
428    dbus::MethodCall method_call(kKWalletInterface, "readEntry");
429    dbus::MessageWriter builder(&method_call);
430    builder.AppendInt32(wallet_handle);  // handle
431    builder.AppendString(folder_name_);  // folder
432    builder.AppendString(signon_realm);  // key
433    builder.AppendString(app_name_);     // appid
434    scoped_ptr<dbus::Response> response(
435        kwallet_proxy_->CallMethodAndBlock(
436            &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
437    if (!response.get()) {
438      LOG(ERROR) << "Error contacting kwalletd (readEntry)";
439      return false;
440    }
441    dbus::MessageReader reader(response.get());
442    const uint8_t* bytes = NULL;
443    size_t length = 0;
444    if (!reader.PopArrayOfBytes(&bytes, &length)) {
445      LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
446                 << response->ToString();
447      return false;
448    }
449    if (!bytes)
450      return false;
451    if (!CheckSerializedValue(bytes, length, signon_realm)) {
452      // This is weird, but we choose not to call it an error. There is an
453      // invalid entry somehow, but by just ignoring it, we make it easier to
454      // repair without having to delete it using kwalletmanager (that is, by
455      // just saving a new password within this realm to overwrite it).
456      return true;
457    }
458
459    // Can't we all just agree on whether bytes are signed or not? Please?
460    Pickle pickle(reinterpret_cast<const char*>(bytes), length);
461    PasswordFormList all_forms;
462    DeserializeValue(signon_realm, pickle, forms);
463  }
464
465  return true;
466}
467
468bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms,
469                                         bool autofillable,
470                                         int wallet_handle) {
471  PasswordFormList all_forms;
472  if (!GetAllLogins(&all_forms, wallet_handle))
473    return false;
474
475  // We have to read all the entries, and then filter them here.
476  forms->reserve(forms->size() + all_forms.size());
477  for (size_t i = 0; i < all_forms.size(); ++i) {
478    if (all_forms[i]->blacklisted_by_user == !autofillable)
479      forms->push_back(all_forms[i]);
480    else
481      delete all_forms[i];
482  }
483
484  return true;
485}
486
487bool NativeBackendKWallet::GetAllLogins(PasswordFormList* forms,
488                                        int wallet_handle) {
489  // We could probably also use readEntryList here.
490  std::vector<std::string> realm_list;
491  {
492    dbus::MethodCall method_call(kKWalletInterface, "entryList");
493    dbus::MessageWriter builder(&method_call);
494    builder.AppendInt32(wallet_handle);  // handle
495    builder.AppendString(folder_name_);  // folder
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 (entryList)";
502      return false;
503    }
504    dbus::MessageReader reader(response.get());
505    if (!reader.PopArrayOfStrings(&realm_list)) {
506      LOG(ERROR) << "Error reading response from kwalletd (entryList): "
507                 << response->ToString();
508      return false;
509    }
510  }
511
512  for (size_t i = 0; i < realm_list.size(); ++i) {
513    const std::string& signon_realm = realm_list[i];
514    dbus::MethodCall method_call(kKWalletInterface, "readEntry");
515    dbus::MessageWriter builder(&method_call);
516    builder.AppendInt32(wallet_handle);  // handle
517    builder.AppendString(folder_name_);  // folder
518    builder.AppendString(signon_realm);  // key
519    builder.AppendString(app_name_);     // appid
520    scoped_ptr<dbus::Response> response(
521        kwallet_proxy_->CallMethodAndBlock(
522            &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
523    if (!response.get()) {
524      LOG(ERROR) << "Error contacting kwalletd (readEntry)";
525      continue;
526    }
527    dbus::MessageReader reader(response.get());
528    const uint8_t* bytes = NULL;
529    size_t length = 0;
530    if (!reader.PopArrayOfBytes(&bytes, &length)) {
531      LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
532                 << response->ToString();
533      continue;
534    }
535    if (!bytes || !CheckSerializedValue(bytes, length, signon_realm))
536      continue;
537
538    // Can't we all just agree on whether bytes are signed or not? Please?
539    Pickle pickle(reinterpret_cast<const char*>(bytes), length);
540    PasswordFormList all_forms;
541    DeserializeValue(signon_realm, pickle, forms);
542  }
543  return true;
544}
545
546bool NativeBackendKWallet::SetLoginsList(const PasswordFormList& forms,
547                                         const std::string& signon_realm,
548                                         int wallet_handle) {
549  if (forms.empty()) {
550    // No items left? Remove the entry from the wallet.
551    dbus::MethodCall method_call(kKWalletInterface, "removeEntry");
552    dbus::MessageWriter builder(&method_call);
553    builder.AppendInt32(wallet_handle);  // handle
554    builder.AppendString(folder_name_);  // folder
555    builder.AppendString(signon_realm);  // key
556    builder.AppendString(app_name_);     // appid
557    scoped_ptr<dbus::Response> response(
558        kwallet_proxy_->CallMethodAndBlock(
559            &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
560    if (!response.get()) {
561      LOG(ERROR) << "Error contacting kwalletd (removeEntry)";
562      return kInvalidKWalletHandle;
563    }
564    dbus::MessageReader reader(response.get());
565    int ret = 0;
566    if (!reader.PopInt32(&ret)) {
567      LOG(ERROR) << "Error reading response from kwalletd (removeEntry): "
568                 << response->ToString();
569      return false;
570    }
571    if (ret != 0)
572      LOG(ERROR) << "Bad return code " << ret << " from KWallet removeEntry";
573    return ret == 0;
574  }
575
576  Pickle value;
577  SerializeValue(forms, &value);
578
579  dbus::MethodCall method_call(kKWalletInterface, "writeEntry");
580  dbus::MessageWriter builder(&method_call);
581  builder.AppendInt32(wallet_handle);  // handle
582  builder.AppendString(folder_name_);  // folder
583  builder.AppendString(signon_realm);  // key
584  builder.AppendArrayOfBytes(static_cast<const uint8_t*>(value.data()),
585                             value.size());  // value
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 (writeEntry)";
592    return kInvalidKWalletHandle;
593  }
594  dbus::MessageReader reader(response.get());
595  int ret = 0;
596  if (!reader.PopInt32(&ret)) {
597    LOG(ERROR) << "Error reading response from kwalletd (writeEntry): "
598               << response->ToString();
599    return false;
600  }
601  if (ret != 0)
602    LOG(ERROR) << "Bad return code " << ret << " from KWallet writeEntry";
603  return ret == 0;
604}
605
606bool NativeBackendKWallet::RemoveLoginsBetween(
607    base::Time delete_begin,
608    base::Time delete_end,
609    TimestampToCompare date_to_compare,
610    password_manager::PasswordStoreChangeList* changes) {
611  DCHECK(changes);
612  changes->clear();
613  int wallet_handle = WalletHandle();
614  if (wallet_handle == kInvalidKWalletHandle)
615    return false;
616
617  // We could probably also use readEntryList here.
618  std::vector<std::string> realm_list;
619  {
620    dbus::MethodCall method_call(kKWalletInterface, "entryList");
621    dbus::MessageWriter builder(&method_call);
622    builder.AppendInt32(wallet_handle);  // handle
623    builder.AppendString(folder_name_);  // folder
624    builder.AppendString(app_name_);     // appid
625    scoped_ptr<dbus::Response> response(kwallet_proxy_->CallMethodAndBlock(
626        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
627    if (!response.get()) {
628      LOG(ERROR) << "Error contacting kwalletd (entryList)";
629      return false;
630    }
631    dbus::MessageReader reader(response.get());
632    dbus::MessageReader array(response.get());
633    if (!reader.PopArray(&array)) {
634      LOG(ERROR) << "Error reading response from kwalletd (entryList): "
635                 << response->ToString();
636      return false;
637    }
638    while (array.HasMoreData()) {
639      std::string realm;
640      if (!array.PopString(&realm)) {
641        LOG(ERROR) << "Error reading response from kwalletd (entryList): "
642                   << response->ToString();
643        return false;
644      }
645      realm_list.push_back(realm);
646    }
647  }
648
649  bool ok = true;
650  for (size_t i = 0; i < realm_list.size(); ++i) {
651    const std::string& signon_realm = realm_list[i];
652    dbus::MethodCall method_call(kKWalletInterface, "readEntry");
653    dbus::MessageWriter builder(&method_call);
654    builder.AppendInt32(wallet_handle);  // handle
655    builder.AppendString(folder_name_);  // folder
656    builder.AppendString(signon_realm);  // key
657    builder.AppendString(app_name_);     // appid
658    scoped_ptr<dbus::Response> response(kwallet_proxy_->CallMethodAndBlock(
659        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
660    if (!response.get()) {
661      LOG(ERROR) << "Error contacting kwalletd (readEntry)";
662      continue;
663    }
664    dbus::MessageReader reader(response.get());
665    const uint8_t* bytes = NULL;
666    size_t length = 0;
667    if (!reader.PopArrayOfBytes(&bytes, &length)) {
668      LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
669                 << response->ToString();
670      continue;
671    }
672    if (!bytes || !CheckSerializedValue(bytes, length, signon_realm))
673      continue;
674
675    // Can't we all just agree on whether bytes are signed or not? Please?
676    Pickle pickle(reinterpret_cast<const char*>(bytes), length);
677    PasswordFormList all_forms;
678    DeserializeValue(signon_realm, pickle, &all_forms);
679
680    PasswordFormList kept_forms;
681    kept_forms.reserve(all_forms.size());
682    base::Time autofill::PasswordForm::*date_member =
683        date_to_compare == CREATION_TIMESTAMP
684            ? &autofill::PasswordForm::date_created
685            : &autofill::PasswordForm::date_synced;
686    for (size_t i = 0; i < all_forms.size(); ++i) {
687      if (delete_begin <= all_forms[i]->*date_member &&
688          (delete_end.is_null() || all_forms[i]->*date_member < delete_end)) {
689        changes->push_back(password_manager::PasswordStoreChange(
690            password_manager::PasswordStoreChange::REMOVE, *all_forms[i]));
691        delete all_forms[i];
692      } else {
693        kept_forms.push_back(all_forms[i]);
694      }
695    }
696
697    if (!SetLoginsList(kept_forms, signon_realm, wallet_handle)) {
698      ok = false;
699      changes->clear();
700    }
701    STLDeleteElements(&kept_forms);
702  }
703  return ok;
704}
705
706// static
707void NativeBackendKWallet::SerializeValue(const PasswordFormList& forms,
708                                          Pickle* pickle) {
709  pickle->WriteInt(kPickleVersion);
710  pickle->WriteUInt64(forms.size());
711  for (PasswordFormList::const_iterator it = forms.begin();
712       it != forms.end(); ++it) {
713    const PasswordForm* form = *it;
714    pickle->WriteInt(form->scheme);
715    pickle->WriteString(form->origin.spec());
716    pickle->WriteString(form->action.spec());
717    pickle->WriteString16(form->username_element);
718    pickle->WriteString16(form->username_value);
719    pickle->WriteString16(form->password_element);
720    pickle->WriteString16(form->password_value);
721    pickle->WriteString16(form->submit_element);
722    pickle->WriteBool(form->ssl_valid);
723    pickle->WriteBool(form->preferred);
724    pickle->WriteBool(form->blacklisted_by_user);
725    pickle->WriteInt64(form->date_created.ToTimeT());
726    pickle->WriteInt(form->type);
727    pickle->WriteInt(form->times_used);
728    autofill::SerializeFormData(form->form_data, pickle);
729    pickle->WriteInt64(form->date_synced.ToInternalValue());
730    pickle->WriteString16(form->display_name);
731    pickle->WriteString(form->avatar_url.spec());
732    pickle->WriteString(form->federation_url.spec());
733    pickle->WriteBool(form->is_zero_click);
734  }
735}
736
737// static
738bool NativeBackendKWallet::DeserializeValueSize(const std::string& signon_realm,
739                                                const PickleIterator& init_iter,
740                                                int version,
741                                                bool size_32,
742                                                bool warn_only,
743                                                PasswordFormList* forms) {
744  PickleIterator iter = init_iter;
745
746  uint64_t count = 0;
747  if (size_32) {
748    uint32_t count_32 = 0;
749    if (!iter.ReadUInt32(&count_32)) {
750      LOG(ERROR) << "Failed to deserialize KWallet entry "
751                 << "(realm: " << signon_realm << ")";
752      return false;
753    }
754    count = count_32;
755  } else {
756    if (!iter.ReadUInt64(&count)) {
757      LOG(ERROR) << "Failed to deserialize KWallet entry "
758                 << "(realm: " << signon_realm << ")";
759      return false;
760    }
761  }
762
763  if (count > 0xFFFF) {
764    // Trying to pin down the cause of http://crbug.com/80728 (or fix it).
765    // This is a very large number of passwords to be saved for a single realm.
766    // It is almost certainly a corrupt pickle and not real data. Ignore it.
767    // This very well might actually be http://crbug.com/107701, so if we're
768    // reading an old pickle, we don't even log this the first time we try to
769    // read it. (That is, when we're reading the native architecture size.)
770    if (!warn_only) {
771      LOG(ERROR) << "Suspiciously large number of entries in KWallet entry "
772                 << "(" << count << "; realm: " << signon_realm << ")";
773    }
774    return false;
775  }
776
777  forms->reserve(forms->size() + count);
778  for (uint64_t i = 0; i < count; ++i) {
779    scoped_ptr<PasswordForm> form(new PasswordForm());
780    form->signon_realm.assign(signon_realm);
781
782    int scheme = 0;
783    int64 date_created = 0;
784    int type = 0;
785    // Note that these will be read back in the order listed due to
786    // short-circuit evaluation. This is important.
787    if (!iter.ReadInt(&scheme) ||
788        !ReadGURL(&iter, warn_only, &form->origin) ||
789        !ReadGURL(&iter, warn_only, &form->action) ||
790        !iter.ReadString16(&form->username_element) ||
791        !iter.ReadString16(&form->username_value) ||
792        !iter.ReadString16(&form->password_element) ||
793        !iter.ReadString16(&form->password_value) ||
794        !iter.ReadString16(&form->submit_element) ||
795        !iter.ReadBool(&form->ssl_valid) ||
796        !iter.ReadBool(&form->preferred) ||
797        !iter.ReadBool(&form->blacklisted_by_user) ||
798        !iter.ReadInt64(&date_created)) {
799      LogDeserializationWarning(version, signon_realm, warn_only);
800      return false;
801    }
802    form->scheme = static_cast<PasswordForm::Scheme>(scheme);
803    form->date_created = base::Time::FromTimeT(date_created);
804
805    if (version > 1) {
806      if (!iter.ReadInt(&type) ||
807          !iter.ReadInt(&form->times_used) ||
808          !autofill::DeserializeFormData(&iter, &form->form_data)) {
809        LogDeserializationWarning(version, signon_realm, false);
810        return false;
811      }
812      form->type = static_cast<PasswordForm::Type>(type);
813    }
814
815    if (version > 2) {
816      int64 date_synced = 0;
817      if (!iter.ReadInt64(&date_synced)) {
818        LogDeserializationWarning(version, signon_realm, false);
819        return false;
820      }
821      form->date_synced = base::Time::FromInternalValue(date_synced);
822    }
823
824    if (version > 3) {
825      if (!iter.ReadString16(&form->display_name) ||
826          !ReadGURL(&iter, warn_only, &form->avatar_url) ||
827          !ReadGURL(&iter, warn_only, &form->federation_url) ||
828          !iter.ReadBool(&form->is_zero_click)) {
829        LogDeserializationWarning(version, signon_realm, false);
830        return false;
831      }
832    }
833
834    forms->push_back(form.release());
835  }
836
837  return true;
838}
839
840// static
841void NativeBackendKWallet::DeserializeValue(const std::string& signon_realm,
842                                            const Pickle& pickle,
843                                            PasswordFormList* forms) {
844  PickleIterator iter(pickle);
845
846  int version = -1;
847  if (!iter.ReadInt(&version) ||
848      version < 0 || version > kPickleVersion) {
849    LOG(ERROR) << "Failed to deserialize KWallet entry "
850               << "(realm: " << signon_realm << ")";
851    return;
852  }
853
854  if (version > 0) {
855    // In current pickles, we expect 64-bit sizes. Failure is an error.
856    DeserializeValueSize(signon_realm, iter, version, false, false, forms);
857    return;
858  }
859
860  const size_t saved_forms_size = forms->size();
861  const bool size_32 = sizeof(size_t) == sizeof(uint32_t);
862  if (!DeserializeValueSize(
863          signon_realm, iter, version, size_32, true, forms)) {
864    // We failed to read the pickle using the native architecture of the system.
865    // Try again with the opposite architecture. Note that we do this even on
866    // 32-bit machines, in case we're reading a 64-bit pickle. (Probably rare,
867    // since mostly we expect upgrades, not downgrades, but both are possible.)
868    forms->resize(saved_forms_size);
869    DeserializeValueSize(signon_realm, iter, version, !size_32, false, forms);
870  }
871}
872
873int NativeBackendKWallet::WalletHandle() {
874  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
875
876  // Open the wallet.
877  // TODO(mdm): Are we leaking these handles? Find out.
878  int32_t handle = kInvalidKWalletHandle;
879  {
880    dbus::MethodCall method_call(kKWalletInterface, "open");
881    dbus::MessageWriter builder(&method_call);
882    builder.AppendString(wallet_name_);  // wallet
883    builder.AppendInt64(0);              // wid
884    builder.AppendString(app_name_);     // appid
885    scoped_ptr<dbus::Response> response(
886        kwallet_proxy_->CallMethodAndBlock(
887            &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
888    if (!response.get()) {
889      LOG(ERROR) << "Error contacting kwalletd (open)";
890      return kInvalidKWalletHandle;
891    }
892    dbus::MessageReader reader(response.get());
893    if (!reader.PopInt32(&handle)) {
894      LOG(ERROR) << "Error reading response from kwalletd (open): "
895                 << response->ToString();
896      return kInvalidKWalletHandle;
897    }
898    if (handle == kInvalidKWalletHandle) {
899      LOG(ERROR) << "Error obtaining KWallet handle";
900      return kInvalidKWalletHandle;
901    }
902  }
903
904  // Check if our folder exists.
905  bool has_folder = false;
906  {
907    dbus::MethodCall method_call(kKWalletInterface, "hasFolder");
908    dbus::MessageWriter builder(&method_call);
909    builder.AppendInt32(handle);         // handle
910    builder.AppendString(folder_name_);  // folder
911    builder.AppendString(app_name_);     // appid
912    scoped_ptr<dbus::Response> response(
913        kwallet_proxy_->CallMethodAndBlock(
914            &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
915    if (!response.get()) {
916      LOG(ERROR) << "Error contacting kwalletd (hasFolder)";
917      return kInvalidKWalletHandle;
918    }
919    dbus::MessageReader reader(response.get());
920    if (!reader.PopBool(&has_folder)) {
921      LOG(ERROR) << "Error reading response from kwalletd (hasFolder): "
922                 << response->ToString();
923      return kInvalidKWalletHandle;
924    }
925  }
926
927  // Create it if it didn't.
928  if (!has_folder) {
929    dbus::MethodCall method_call(kKWalletInterface, "createFolder");
930    dbus::MessageWriter builder(&method_call);
931    builder.AppendInt32(handle);         // handle
932    builder.AppendString(folder_name_);  // folder
933    builder.AppendString(app_name_);     // appid
934    scoped_ptr<dbus::Response> response(
935        kwallet_proxy_->CallMethodAndBlock(
936            &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
937    if (!response.get()) {
938      LOG(ERROR) << "Error contacting kwalletd (createFolder)";
939      return kInvalidKWalletHandle;
940    }
941    dbus::MessageReader reader(response.get());
942    bool success = false;
943    if (!reader.PopBool(&success)) {
944      LOG(ERROR) << "Error reading response from kwalletd (createFolder): "
945                 << response->ToString();
946      return kInvalidKWalletHandle;
947    }
948    if (!success) {
949      LOG(ERROR) << "Error creating KWallet folder";
950      return kInvalidKWalletHandle;
951    }
952  }
953
954  return handle;
955}
956
957std::string NativeBackendKWallet::GetProfileSpecificFolderName() const {
958  // Originally, the folder name was always just "Chrome Form Data".
959  // Now we use it to distinguish passwords for different profiles.
960  return base::StringPrintf("%s (%d)", kKWalletFolder, profile_id_);
961}
962