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