• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
NativeBackendKWallet()31 NativeBackendKWallet::NativeBackendKWallet()
32     : error_(NULL),
33       connection_(NULL),
34       proxy_(NULL) {
35 }
36 
~NativeBackendKWallet()37 NativeBackendKWallet::~NativeBackendKWallet() {
38   if (proxy_)
39     g_object_unref(proxy_);
40 }
41 
Init()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 
StartKWalletd()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 
InitWallet()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 
AddLogin(const PasswordForm & form)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 
UpdateLogin(const PasswordForm & form)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 
RemoveLogin(const PasswordForm & form)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 
RemoveLoginsCreatedBetween(const base::Time & delete_begin,const base::Time & delete_end)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 
GetLogins(const PasswordForm & form,PasswordFormList * forms)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 
GetLoginsCreatedBetween(const base::Time & get_begin,const base::Time & get_end,PasswordFormList * forms)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 
GetAutofillableLogins(PasswordFormList * forms)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 
GetBlacklistLogins(PasswordFormList * forms)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 
GetLoginsList(PasswordFormList * forms,const string & signon_realm,int wallet_handle)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 
GetLoginsList(PasswordFormList * forms,bool autofillable,int wallet_handle)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 
GetLoginsList(PasswordFormList * forms,const base::Time & begin,const base::Time & end,int wallet_handle)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 
GetAllLogins(PasswordFormList * forms,int wallet_handle)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 
SetLoginsList(const PasswordFormList & forms,const string & signon_realm,int wallet_handle)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 
CompareForms(const PasswordForm & a,const PasswordForm & b,bool update_check)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 
SerializeValue(const PasswordFormList & forms,Pickle * pickle)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 
CheckSerializedValue(const GArray * byte_array,const char * realm)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 
DeserializeValue(const string & signon_realm,const Pickle & pickle,PasswordFormList * forms)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 
ReadGURL(const Pickle & pickle,void ** iter,GURL * url)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 
CheckError()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 
WalletHandle()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