native_backend_kwallet_x.cc revision 731df977c0511bca2206b5f333555b1205ff1f43
1// Copyright (c) 2010 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 "chrome/browser/browser_thread.h"
14
15using std::string;
16using std::vector;
17using 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.
21const char* NativeBackendKWallet::kAppId = "Chrome";
22const char* NativeBackendKWallet::kKWalletFolder = "Chrome Form Data";
23
24const char* NativeBackendKWallet::kKWalletServiceName = "org.kde.kwalletd";
25const char* NativeBackendKWallet::kKWalletPath = "/modules/kwalletd";
26const char* NativeBackendKWallet::kKWalletInterface = "org.kde.KWallet";
27const char* NativeBackendKWallet::kKLauncherServiceName = "org.kde.klauncher";
28const char* NativeBackendKWallet::kKLauncherPath = "/KLauncher";
29const char* NativeBackendKWallet::kKLauncherInterface = "org.kde.KLauncher";
30
31NativeBackendKWallet::NativeBackendKWallet()
32    : error_(NULL),
33      connection_(NULL),
34      proxy_(NULL) {
35}
36
37NativeBackendKWallet::~NativeBackendKWallet() {
38  if (proxy_)
39    g_object_unref(proxy_);
40}
41
42bool 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
57bool 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
93bool 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
122bool 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
137bool 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
156bool 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
180bool 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
241bool 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
249bool 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
258bool 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
265bool 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
272bool 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
321bool 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
340bool 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
362bool 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
400bool 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
447bool 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
460void 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
482bool 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
494void NativeBackendKWallet::DeserializeValue(const string& signon_realm,
495                                            const Pickle& pickle,
496                                            PasswordFormList* forms) {
497  void* iter = NULL;
498
499  int version = -1;
500  pickle.ReadInt(&iter, &version);
501  if (version != kPickleVersion) {
502    // This is the only version so far, so anything else is an error.
503    return;
504  }
505
506  size_t count = 0;
507  pickle.ReadSize(&iter, &count);
508
509  forms->reserve(forms->size() + count);
510  for (size_t i = 0; i < count; ++i) {
511    PasswordForm* form = new PasswordForm();
512    form->signon_realm.assign(signon_realm);
513
514    int scheme = 0;
515    pickle.ReadInt(&iter, &scheme);
516    form->scheme = static_cast<PasswordForm::Scheme>(scheme);
517    ReadGURL(pickle, &iter, &form->origin);
518    ReadGURL(pickle, &iter, &form->action);
519    pickle.ReadString16(&iter, &form->username_element);
520    pickle.ReadString16(&iter, &form->username_value);
521    pickle.ReadString16(&iter, &form->password_element);
522    pickle.ReadString16(&iter, &form->password_value);
523    pickle.ReadString16(&iter, &form->submit_element);
524    pickle.ReadBool(&iter, &form->ssl_valid);
525    pickle.ReadBool(&iter, &form->preferred);
526    pickle.ReadBool(&iter, &form->blacklisted_by_user);
527    int64 date_created = 0;
528    pickle.ReadInt64(&iter, &date_created);
529    form->date_created = base::Time::FromTimeT(date_created);
530    forms->push_back(form);
531  }
532}
533
534void NativeBackendKWallet::ReadGURL(const Pickle& pickle, void** iter,
535                                    GURL* url) {
536  string url_string;
537  pickle.ReadString(iter, &url_string);
538  *url = GURL(url_string);
539}
540
541bool NativeBackendKWallet::CheckError() {
542  if (error_) {
543    LOG(ERROR) << "Failed to complete KWallet call: " << error_->message;
544    g_error_free(error_);
545    error_ = NULL;
546    return true;
547  }
548  return false;
549}
550
551int NativeBackendKWallet::WalletHandle() {
552  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
553  // Open the wallet.
554  int handle = kInvalidKWalletHandle;
555  dbus_g_proxy_call(proxy_, "open", &error_,
556                    G_TYPE_STRING, wallet_name_.c_str(),  // wallet
557                    G_TYPE_INT64,  0LL,                   // wid
558                    G_TYPE_STRING, kAppId,                // appid
559                    G_TYPE_INVALID,
560                    G_TYPE_INT,    &handle,
561                    G_TYPE_INVALID);
562  if (CheckError() || handle == kInvalidKWalletHandle)
563    return kInvalidKWalletHandle;
564
565  // Check if our folder exists.
566  gboolean has_folder = false;
567  dbus_g_proxy_call(proxy_, "hasFolder", &error_,
568                    G_TYPE_INT,    handle,          // handle
569                    G_TYPE_STRING, kKWalletFolder,  // folder
570                    G_TYPE_STRING, kAppId,          // appid
571                    G_TYPE_INVALID,
572                    G_TYPE_BOOLEAN, &has_folder,
573                    G_TYPE_INVALID);
574  if (CheckError())
575    return kInvalidKWalletHandle;
576
577  // Create it if it didn't.
578  if (!has_folder) {
579    gboolean success = false;
580    dbus_g_proxy_call(proxy_, "createFolder", &error_,
581                      G_TYPE_INT,    handle,          // handle
582                      G_TYPE_STRING, kKWalletFolder,  // folder
583                      G_TYPE_STRING, kAppId,          // appid
584                      G_TYPE_INVALID,
585                      G_TYPE_BOOLEAN, &success,
586                      G_TYPE_INVALID);
587    if (CheckError() || !success)
588      return kInvalidKWalletHandle;
589  }
590
591  return handle;
592}
593