Home | History | Annotate | Download | only in password_manager
      1 // Copyright (c) 2011 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 <sstream>
      8 
      9 #include "base/logging.h"
     10 #include "base/pickle.h"
     11 #include "base/stl_util-inl.h"
     12 #include "base/string_util.h"
     13 #include "content/browser/browser_thread.h"
     14 
     15 using std::string;
     16 using std::vector;
     17 using webkit_glue::PasswordForm;
     18 
     19 // We could localize these strings, but then changing your locale would cause
     20 // you to lose access to all your stored passwords. Maybe best not to do that.
     21 const char* NativeBackendKWallet::kAppId = "Chrome";
     22 const char* NativeBackendKWallet::kKWalletFolder = "Chrome Form Data";
     23 
     24 const char* NativeBackendKWallet::kKWalletServiceName = "org.kde.kwalletd";
     25 const char* NativeBackendKWallet::kKWalletPath = "/modules/kwalletd";
     26 const char* NativeBackendKWallet::kKWalletInterface = "org.kde.KWallet";
     27 const char* NativeBackendKWallet::kKLauncherServiceName = "org.kde.klauncher";
     28 const char* NativeBackendKWallet::kKLauncherPath = "/KLauncher";
     29 const char* NativeBackendKWallet::kKLauncherInterface = "org.kde.KLauncher";
     30 
     31 NativeBackendKWallet::NativeBackendKWallet()
     32     : error_(NULL),
     33       connection_(NULL),
     34       proxy_(NULL) {
     35 }
     36 
     37 NativeBackendKWallet::~NativeBackendKWallet() {
     38   if (proxy_)
     39     g_object_unref(proxy_);
     40 }
     41 
     42 bool NativeBackendKWallet::Init() {
     43   // Get a connection to the session bus.
     44   connection_ = dbus_g_bus_get(DBUS_BUS_SESSION, &error_);
     45   if (CheckError())
     46     return false;
     47 
     48   if (!InitWallet()) {
     49     // kwalletd may not be running. Try to start it and try again.
     50     if (!StartKWalletd() || !InitWallet())
     51       return false;
     52   }
     53 
     54   return true;
     55 }
     56 
     57 bool NativeBackendKWallet::StartKWalletd() {
     58   // Sadly kwalletd doesn't use DBUS activation, so we have to make a call to
     59   // klauncher to start it.
     60   DBusGProxy* klauncher_proxy =
     61       dbus_g_proxy_new_for_name(connection_, kKLauncherServiceName,
     62                                 kKLauncherPath, kKLauncherInterface);
     63 
     64   char* empty_string_list = NULL;
     65   int ret = 1;
     66   char* error = NULL;
     67   dbus_g_proxy_call(klauncher_proxy, "start_service_by_desktop_name", &error_,
     68                     G_TYPE_STRING,  "kwalletd",          // serviceName
     69                     G_TYPE_STRV,    &empty_string_list,  // urls
     70                     G_TYPE_STRV,    &empty_string_list,  // envs
     71                     G_TYPE_STRING,  "",                  // startup_id
     72                     G_TYPE_BOOLEAN, (gboolean) false,    // blind
     73                     G_TYPE_INVALID,
     74                     G_TYPE_INT,     &ret,                // result
     75                     G_TYPE_STRING,  NULL,                // dubsName
     76                     G_TYPE_STRING,  &error,              // error
     77                     G_TYPE_INT,     NULL,                // pid
     78                     G_TYPE_INVALID);
     79 
     80   if (error && *error) {
     81     LOG(ERROR) << "Error launching kwalletd: " << error;
     82     ret = 1;  // Make sure we return false after freeing.
     83   }
     84 
     85   g_free(error);
     86   g_object_unref(klauncher_proxy);
     87 
     88   if (CheckError() || ret != 0)
     89     return false;
     90   return true;
     91 }
     92 
     93 bool NativeBackendKWallet::InitWallet() {
     94   // Make a proxy to KWallet.
     95   proxy_ = dbus_g_proxy_new_for_name(connection_, kKWalletServiceName,
     96                                      kKWalletPath, kKWalletInterface);
     97 
     98   // Check KWallet is enabled.
     99   gboolean is_enabled = false;
    100   dbus_g_proxy_call(proxy_, "isEnabled", &error_,
    101                     G_TYPE_INVALID,
    102                     G_TYPE_BOOLEAN, &is_enabled,
    103                     G_TYPE_INVALID);
    104   if (CheckError() || !is_enabled)
    105     return false;
    106 
    107   // Get the wallet name.
    108   char* wallet_name = NULL;
    109   dbus_g_proxy_call(proxy_, "networkWallet", &error_,
    110                     G_TYPE_INVALID,
    111                     G_TYPE_STRING, &wallet_name,
    112                     G_TYPE_INVALID);
    113   if (CheckError() || !wallet_name)
    114     return false;
    115 
    116   wallet_name_.assign(wallet_name);
    117   g_free(wallet_name);
    118 
    119   return true;
    120 }
    121 
    122 bool NativeBackendKWallet::AddLogin(const PasswordForm& form) {
    123   int wallet_handle = WalletHandle();
    124   if (wallet_handle == kInvalidKWalletHandle)
    125     return false;
    126 
    127   PasswordFormList forms;
    128   GetLoginsList(&forms, form.signon_realm, wallet_handle);
    129 
    130   forms.push_back(new PasswordForm(form));
    131   bool ok = SetLoginsList(forms, form.signon_realm, wallet_handle);
    132 
    133   STLDeleteElements(&forms);
    134   return ok;
    135 }
    136 
    137 bool NativeBackendKWallet::UpdateLogin(const PasswordForm& form) {
    138   int wallet_handle = WalletHandle();
    139   if (wallet_handle == kInvalidKWalletHandle)
    140     return false;
    141 
    142   PasswordFormList forms;
    143   GetLoginsList(&forms, form.signon_realm, wallet_handle);
    144 
    145   for (size_t i = 0; i < forms.size(); ++i) {
    146     if (CompareForms(form, *forms[i], true))
    147       *forms[i] = form;
    148   }
    149 
    150   bool ok = SetLoginsList(forms, form.signon_realm, wallet_handle);
    151 
    152   STLDeleteElements(&forms);
    153   return ok;
    154 }
    155 
    156 bool NativeBackendKWallet::RemoveLogin(const PasswordForm& form) {
    157   int wallet_handle = WalletHandle();
    158   if (wallet_handle == kInvalidKWalletHandle)
    159     return false;
    160 
    161   PasswordFormList all_forms;
    162   GetLoginsList(&all_forms, form.signon_realm, wallet_handle);
    163 
    164   PasswordFormList kept_forms;
    165   kept_forms.reserve(all_forms.size());
    166   for (size_t i = 0; i < all_forms.size(); ++i) {
    167     if (CompareForms(form, *all_forms[i], false))
    168       delete all_forms[i];
    169     else
    170       kept_forms.push_back(all_forms[i]);
    171   }
    172 
    173   // Update the entry in the wallet, possibly deleting it.
    174   bool ok = SetLoginsList(kept_forms, form.signon_realm, wallet_handle);
    175 
    176   STLDeleteElements(&kept_forms);
    177   return ok;
    178 }
    179 
    180 bool NativeBackendKWallet::RemoveLoginsCreatedBetween(
    181     const base::Time& delete_begin,
    182     const base::Time& delete_end) {
    183   int wallet_handle = WalletHandle();
    184   if (wallet_handle == kInvalidKWalletHandle)
    185     return false;
    186 
    187   // We could probably also use readEntryList here.
    188   char** realm_list = NULL;
    189   dbus_g_proxy_call(proxy_, "entryList", &error_,
    190                     G_TYPE_INT,     wallet_handle,             // handle
    191                     G_TYPE_STRING,  kKWalletFolder,            // folder
    192                     G_TYPE_STRING,  kAppId,                    // appid
    193                     G_TYPE_INVALID,
    194                     G_TYPE_STRV,    &realm_list,
    195                     G_TYPE_INVALID);
    196   if (CheckError())
    197     return false;
    198 
    199   bool ok = true;
    200   for (char** realm = realm_list; *realm; ++realm) {
    201     GArray* byte_array = NULL;
    202     dbus_g_proxy_call(proxy_, "readEntry", &error_,
    203                       G_TYPE_INT,     wallet_handle,           // handle
    204                       G_TYPE_STRING,  kKWalletFolder,          // folder
    205                       G_TYPE_STRING,  *realm,                  // key
    206                       G_TYPE_STRING,  kAppId,                  // appid
    207                       G_TYPE_INVALID,
    208                       DBUS_TYPE_G_UCHAR_ARRAY, &byte_array,
    209                       G_TYPE_INVALID);
    210 
    211     if (CheckError() || !byte_array ||
    212         !CheckSerializedValue(byte_array, *realm)) {
    213       continue;
    214     }
    215 
    216     string signon_realm(*realm);
    217     Pickle pickle(byte_array->data, byte_array->len);
    218     PasswordFormList all_forms;
    219     DeserializeValue(signon_realm, pickle, &all_forms);
    220     g_array_free(byte_array, true);
    221 
    222     PasswordFormList kept_forms;
    223     kept_forms.reserve(all_forms.size());
    224     for (size_t i = 0; i < all_forms.size(); ++i) {
    225       if (delete_begin <= all_forms[i]->date_created &&
    226           (delete_end.is_null() || all_forms[i]->date_created < delete_end)) {
    227         delete all_forms[i];
    228       } else {
    229         kept_forms.push_back(all_forms[i]);
    230       }
    231     }
    232 
    233     if (!SetLoginsList(kept_forms, signon_realm, wallet_handle))
    234       ok = false;
    235     STLDeleteElements(&kept_forms);
    236   }
    237   g_strfreev(realm_list);
    238   return ok;
    239 }
    240 
    241 bool NativeBackendKWallet::GetLogins(const PasswordForm& form,
    242                                      PasswordFormList* forms) {
    243   int wallet_handle = WalletHandle();
    244   if (wallet_handle == kInvalidKWalletHandle)
    245     return false;
    246   return GetLoginsList(forms, form.signon_realm, wallet_handle);
    247 }
    248 
    249 bool NativeBackendKWallet::GetLoginsCreatedBetween(const base::Time& get_begin,
    250                                                    const base::Time& get_end,
    251                                                    PasswordFormList* forms) {
    252   int wallet_handle = WalletHandle();
    253   if (wallet_handle == kInvalidKWalletHandle)
    254     return false;
    255   return GetLoginsList(forms, get_begin, get_end, wallet_handle);
    256 }
    257 
    258 bool NativeBackendKWallet::GetAutofillableLogins(PasswordFormList* forms) {
    259   int wallet_handle = WalletHandle();
    260   if (wallet_handle == kInvalidKWalletHandle)
    261     return false;
    262   return GetLoginsList(forms, true, wallet_handle);
    263 }
    264 
    265 bool NativeBackendKWallet::GetBlacklistLogins(PasswordFormList* forms) {
    266   int wallet_handle = WalletHandle();
    267   if (wallet_handle == kInvalidKWalletHandle)
    268     return false;
    269   return GetLoginsList(forms, false, wallet_handle);
    270 }
    271 
    272 bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms,
    273                                          const string& signon_realm,
    274                                          int wallet_handle) {
    275   // Is there an entry in the wallet?
    276   gboolean has_entry = false;
    277   dbus_g_proxy_call(proxy_, "hasEntry", &error_,
    278                     G_TYPE_INT,     wallet_handle,         // handle
    279                     G_TYPE_STRING,  kKWalletFolder,        // folder
    280                     G_TYPE_STRING,  signon_realm.c_str(),  // key
    281                     G_TYPE_STRING,  kAppId,                // appid
    282                     G_TYPE_INVALID,
    283                     G_TYPE_BOOLEAN, &has_entry,
    284                     G_TYPE_INVALID);
    285 
    286   if (CheckError())
    287     return false;
    288   if (!has_entry) {
    289     // This is not an error. There just isn't a matching entry.
    290     return true;
    291   }
    292 
    293   GArray* byte_array = NULL;
    294   dbus_g_proxy_call(proxy_, "readEntry", &error_,
    295                     G_TYPE_INT,     wallet_handle,         // handle
    296                     G_TYPE_STRING,  kKWalletFolder,        // folder
    297                     G_TYPE_STRING,  signon_realm.c_str(),  // key
    298                     G_TYPE_STRING,  kAppId,                // appid
    299                     G_TYPE_INVALID,
    300                     DBUS_TYPE_G_UCHAR_ARRAY, &byte_array,
    301                     G_TYPE_INVALID);
    302 
    303   if (CheckError() || !byte_array)
    304     return false;
    305   if (!CheckSerializedValue(byte_array, signon_realm.c_str())) {
    306     // This is weird, but we choose not to call it an error. There's an invalid
    307     // entry somehow, but by pretending it just doesn't exist, we make it easier
    308     // to repair without having to delete it using kwalletmanager (that is, by
    309     // just saving a new password within this realm to overwrite it).
    310     g_array_free(byte_array, true);
    311     return true;
    312   }
    313 
    314   Pickle pickle(byte_array->data, byte_array->len);
    315   DeserializeValue(signon_realm, pickle, forms);
    316   g_array_free(byte_array, true);
    317 
    318   return true;
    319 }
    320 
    321 bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms,
    322                                          bool autofillable,
    323                                          int wallet_handle) {
    324   PasswordFormList all_forms;
    325   if (!GetAllLogins(&all_forms, wallet_handle))
    326     return false;
    327 
    328   // We have to read all the entries, and then filter them here.
    329   forms->reserve(forms->size() + all_forms.size());
    330   for (size_t i = 0; i < all_forms.size(); ++i) {
    331     if (all_forms[i]->blacklisted_by_user == !autofillable)
    332       forms->push_back(all_forms[i]);
    333     else
    334       delete all_forms[i];
    335   }
    336 
    337   return true;
    338 }
    339 
    340 bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms,
    341                                          const base::Time& begin,
    342                                          const base::Time& end,
    343                                          int wallet_handle) {
    344   PasswordFormList all_forms;
    345   if (!GetAllLogins(&all_forms, wallet_handle))
    346     return false;
    347 
    348   // We have to read all the entries, and then filter them here.
    349   forms->reserve(forms->size() + all_forms.size());
    350   for (size_t i = 0; i < all_forms.size(); ++i) {
    351     if (begin <= all_forms[i]->date_created &&
    352         (end.is_null() || all_forms[i]->date_created < end)) {
    353       forms->push_back(all_forms[i]);
    354     } else {
    355       delete all_forms[i];
    356     }
    357   }
    358 
    359   return true;
    360 }
    361 
    362 bool NativeBackendKWallet::GetAllLogins(PasswordFormList* forms,
    363                                         int wallet_handle) {
    364   // We could probably also use readEntryList here.
    365   char** realm_list = NULL;
    366   dbus_g_proxy_call(proxy_, "entryList", &error_,
    367                     G_TYPE_INT,     wallet_handle,             // handle
    368                     G_TYPE_STRING,  kKWalletFolder,            // folder
    369                     G_TYPE_STRING,  kAppId,                    // appid
    370                     G_TYPE_INVALID,
    371                     G_TYPE_STRV,    &realm_list,
    372                     G_TYPE_INVALID);
    373   if (CheckError())
    374     return false;
    375 
    376   for (char** realm = realm_list; *realm; ++realm) {
    377     GArray* byte_array = NULL;
    378     dbus_g_proxy_call(proxy_, "readEntry", &error_,
    379                       G_TYPE_INT,     wallet_handle,           // handle
    380                       G_TYPE_STRING,  kKWalletFolder,          // folder
    381                       G_TYPE_STRING,  *realm,                  // key
    382                       G_TYPE_STRING,  kAppId,                  // appid
    383                       G_TYPE_INVALID,
    384                       DBUS_TYPE_G_UCHAR_ARRAY, &byte_array,
    385                       G_TYPE_INVALID);
    386 
    387     if (CheckError() || !byte_array ||
    388         !CheckSerializedValue(byte_array, *realm)) {
    389       continue;
    390     }
    391 
    392     Pickle pickle(byte_array->data, byte_array->len);
    393     DeserializeValue(*realm, pickle, forms);
    394     g_array_free(byte_array, true);
    395   }
    396   g_strfreev(realm_list);
    397   return true;
    398 }
    399 
    400 bool NativeBackendKWallet::SetLoginsList(const PasswordFormList& forms,
    401                                          const string& signon_realm,
    402                                          int wallet_handle) {
    403   if (forms.empty()) {
    404     // No items left? Remove the entry from the wallet.
    405     int ret = 0;
    406     dbus_g_proxy_call(proxy_, "removeEntry", &error_,
    407                       G_TYPE_INT,     wallet_handle,         // handle
    408                       G_TYPE_STRING,  kKWalletFolder,        // folder
    409                       G_TYPE_STRING,  signon_realm.c_str(),  // key
    410                       G_TYPE_STRING,  kAppId,                // appid
    411                       G_TYPE_INVALID,
    412                       G_TYPE_INT,     &ret,
    413                       G_TYPE_INVALID);
    414     CheckError();
    415     if (ret != 0)
    416       LOG(ERROR) << "Bad return code " << ret << " from KWallet removeEntry";
    417     return ret == 0;
    418   }
    419 
    420   Pickle value;
    421   SerializeValue(forms, &value);
    422 
    423   // Convert the pickled bytes to a GByteArray.
    424   GArray* byte_array = g_array_sized_new(false, false, sizeof(char),
    425                                          value.size());
    426   g_array_append_vals(byte_array, value.data(), value.size());
    427 
    428   // Make the call.
    429   int ret = 0;
    430   dbus_g_proxy_call(proxy_, "writeEntry", &error_,
    431                     G_TYPE_INT,           wallet_handle,         // handle
    432                     G_TYPE_STRING,        kKWalletFolder,        // folder
    433                     G_TYPE_STRING,        signon_realm.c_str(),  // key
    434                     DBUS_TYPE_G_UCHAR_ARRAY, byte_array,         // value
    435                     G_TYPE_STRING,        kAppId,                // appid
    436                     G_TYPE_INVALID,
    437                     G_TYPE_INT,           &ret,
    438                     G_TYPE_INVALID);
    439   g_array_free(byte_array, true);
    440 
    441   CheckError();
    442   if (ret != 0)
    443     LOG(ERROR) << "Bad return code " << ret << " from KWallet writeEntry";
    444   return ret == 0;
    445 }
    446 
    447 bool NativeBackendKWallet::CompareForms(const PasswordForm& a,
    448                                         const PasswordForm& b,
    449                                         bool update_check) {
    450   // An update check doesn't care about the submit element.
    451   if (!update_check && a.submit_element != b.submit_element)
    452     return false;
    453   return a.origin           == b.origin &&
    454          a.password_element == b.password_element &&
    455          a.signon_realm     == b.signon_realm &&
    456          a.username_element == b.username_element &&
    457          a.username_value   == b.username_value;
    458 }
    459 
    460 void NativeBackendKWallet::SerializeValue(const PasswordFormList& forms,
    461                                           Pickle* pickle) {
    462   pickle->WriteInt(kPickleVersion);
    463   pickle->WriteSize(forms.size());
    464   for (PasswordFormList::const_iterator it = forms.begin() ;
    465        it != forms.end() ; ++it) {
    466     const PasswordForm* form = *it;
    467     pickle->WriteInt(form->scheme);
    468     pickle->WriteString(form->origin.spec());
    469     pickle->WriteString(form->action.spec());
    470     pickle->WriteString16(form->username_element);
    471     pickle->WriteString16(form->username_value);
    472     pickle->WriteString16(form->password_element);
    473     pickle->WriteString16(form->password_value);
    474     pickle->WriteString16(form->submit_element);
    475     pickle->WriteBool(form->ssl_valid);
    476     pickle->WriteBool(form->preferred);
    477     pickle->WriteBool(form->blacklisted_by_user);
    478     pickle->WriteInt64(form->date_created.ToTimeT());
    479   }
    480 }
    481 
    482 bool NativeBackendKWallet::CheckSerializedValue(const GArray* byte_array,
    483                                                 const char* realm) {
    484   const Pickle::Header* header =
    485       reinterpret_cast<const Pickle::Header*>(byte_array->data);
    486   if (byte_array->len < sizeof(*header) ||
    487       header->payload_size > byte_array->len - sizeof(*header)) {
    488     LOG(WARNING) << "Invalid KWallet entry detected (realm: " << realm << ")";
    489     return false;
    490   }
    491   return true;
    492 }
    493 
    494 void NativeBackendKWallet::DeserializeValue(const string& signon_realm,
    495                                             const Pickle& pickle,
    496                                             PasswordFormList* forms) {
    497   void* iter = NULL;
    498 
    499   int version = -1;
    500   if (!pickle.ReadInt(&iter, &version) || version != kPickleVersion) {
    501     // This is the only version so far, so anything else is an error.
    502     LOG(ERROR) << "Failed to deserialize KWallet entry "
    503                << "(realm: " << signon_realm << ")";
    504     return;
    505   }
    506 
    507   size_t count = 0;
    508   if (!pickle.ReadSize(&iter, &count)) {
    509     LOG(ERROR) << "Failed to deserialize KWallet entry "
    510                << "(realm: " << signon_realm << ")";
    511     return;
    512   }
    513 
    514   forms->reserve(forms->size() + count);
    515   for (size_t i = 0; i < count; ++i) {
    516     scoped_ptr<PasswordForm> form(new PasswordForm());
    517     form->signon_realm.assign(signon_realm);
    518 
    519     int scheme = 0;
    520     int64 date_created = 0;
    521     // Note that these will be read back in the order listed due to
    522     // short-circuit evaluation. This is important.
    523     if (!pickle.ReadInt(&iter, &scheme) ||
    524         !ReadGURL(pickle, &iter, &form->origin) ||
    525         !ReadGURL(pickle, &iter, &form->action) ||
    526         !pickle.ReadString16(&iter, &form->username_element) ||
    527         !pickle.ReadString16(&iter, &form->username_value) ||
    528         !pickle.ReadString16(&iter, &form->password_element) ||
    529         !pickle.ReadString16(&iter, &form->password_value) ||
    530         !pickle.ReadString16(&iter, &form->submit_element) ||
    531         !pickle.ReadBool(&iter, &form->ssl_valid) ||
    532         !pickle.ReadBool(&iter, &form->preferred) ||
    533         !pickle.ReadBool(&iter, &form->blacklisted_by_user) ||
    534         !pickle.ReadInt64(&iter, &date_created)) {
    535       LOG(ERROR) << "Failed to deserialize KWallet entry "
    536                  << "(realm: " << signon_realm << ")";
    537       break;
    538     }
    539     form->scheme = static_cast<PasswordForm::Scheme>(scheme);
    540     form->date_created = base::Time::FromTimeT(date_created);
    541     forms->push_back(form.release());
    542   }
    543 }
    544 
    545 bool NativeBackendKWallet::ReadGURL(const Pickle& pickle, void** iter,
    546                                     GURL* url) {
    547   string url_string;
    548   if (!pickle.ReadString(iter, &url_string)) {
    549     LOG(ERROR) << "Failed to deserialize URL";
    550     *url = GURL();
    551     return false;
    552   }
    553   *url = GURL(url_string);
    554   return true;
    555 }
    556 
    557 bool NativeBackendKWallet::CheckError() {
    558   if (error_) {
    559     LOG(ERROR) << "Failed to complete KWallet call: " << error_->message;
    560     g_error_free(error_);
    561     error_ = NULL;
    562     return true;
    563   }
    564   return false;
    565 }
    566 
    567 int NativeBackendKWallet::WalletHandle() {
    568   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    569   // Open the wallet.
    570   int handle = kInvalidKWalletHandle;
    571   dbus_g_proxy_call(proxy_, "open", &error_,
    572                     G_TYPE_STRING, wallet_name_.c_str(),  // wallet
    573                     G_TYPE_INT64,  0LL,                   // wid
    574                     G_TYPE_STRING, kAppId,                // appid
    575                     G_TYPE_INVALID,
    576                     G_TYPE_INT,    &handle,
    577                     G_TYPE_INVALID);
    578   if (CheckError() || handle == kInvalidKWalletHandle)
    579     return kInvalidKWalletHandle;
    580 
    581   // Check if our folder exists.
    582   gboolean has_folder = false;
    583   dbus_g_proxy_call(proxy_, "hasFolder", &error_,
    584                     G_TYPE_INT,    handle,          // handle
    585                     G_TYPE_STRING, kKWalletFolder,  // folder
    586                     G_TYPE_STRING, kAppId,          // appid
    587                     G_TYPE_INVALID,
    588                     G_TYPE_BOOLEAN, &has_folder,
    589                     G_TYPE_INVALID);
    590   if (CheckError())
    591     return kInvalidKWalletHandle;
    592 
    593   // Create it if it didn't.
    594   if (!has_folder) {
    595     gboolean success = false;
    596     dbus_g_proxy_call(proxy_, "createFolder", &error_,
    597                       G_TYPE_INT,    handle,          // handle
    598                       G_TYPE_STRING, kKWalletFolder,  // folder
    599                       G_TYPE_STRING, kAppId,          // appid
    600                       G_TYPE_INVALID,
    601                       G_TYPE_BOOLEAN, &success,
    602                       G_TYPE_INVALID);
    603     if (CheckError() || !success)
    604       return kInvalidKWalletHandle;
    605   }
    606 
    607   return handle;
    608 }
    609