Home | History | Annotate | Download | only in password_manager
      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 
     25 using autofill::PasswordForm;
     26 using content::BrowserThread;
     27 
     28 namespace {
     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.
     33 const char kKWalletFolder[] = "Chrome Form Data";
     34 
     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";
     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.
     47 bool CompareForms(const autofill::PasswordForm& a,
     48                   const autofill::PasswordForm& b,
     49                   bool update_check) {
     50   // An update check doesn't care about the submit element.
     51   if (!update_check && a.submit_element != b.submit_element)
     52     return false;
     53   return a.origin           == b.origin &&
     54          a.password_element == b.password_element &&
     55          a.signon_realm     == b.signon_realm &&
     56          a.username_element == b.username_element &&
     57          a.username_value   == b.username_value;
     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.
     62 bool CheckSerializedValue(const uint8_t* byte_array,
     63                           size_t length,
     64                           const std::string& realm) {
     65   const Pickle::Header* header =
     66       reinterpret_cast<const Pickle::Header*>(byte_array);
     67   if (length < sizeof(*header) ||
     68       header->payload_size > length - sizeof(*header)) {
     69     LOG(WARNING) << "Invalid KWallet entry detected (realm: " << realm << ")";
     70     return false;
     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.
     77 bool ReadGURL(PickleIterator* iter, bool warn_only, GURL* url) {
     78   std::string url_string;
     79   if (!iter->ReadString(&url_string)) {
     80     if (!warn_only)
     81       LOG(ERROR) << "Failed to deserialize URL.";
     82     *url = GURL();
     83     return false;
     84   }
     85   *url = GURL(url_string);
     86   return true;
     87 }
     88 
     89 void 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 
    105 NativeBackendKWallet::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 
    112 NativeBackendKWallet::~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 
    126 bool NativeBackendKWallet::Init() {
    127   // Without the |optional_bus| parameter, a real bus will be instantiated.
    128   return InitWithBus(scoped_refptr<dbus::Bus>());
    129 }
    130 
    131 bool 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 
    150 void 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 
    177 bool 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 
    221 NativeBackendKWallet::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 
    268 password_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 
    304 bool 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 
    335 bool 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 
    359 bool 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 
    367 bool 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 
    374 bool 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 
    382 bool 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 
    389 bool 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 
    396 bool 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 
    468 bool 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 
    487 bool 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 
    546 bool 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 
    606 bool 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
    707 void 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
    738 bool 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
    841 void 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 
    873 int 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 
    957 std::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