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_gnome_x.h"
      6 
      7 #include <dlfcn.h>
      8 #include <gnome-keyring.h>
      9 
     10 #include <map>
     11 #include <string>
     12 #include <vector>
     13 
     14 #include "base/basictypes.h"
     15 #include "base/logging.h"
     16 #include "base/memory/scoped_ptr.h"
     17 #include "base/metrics/histogram.h"
     18 #include "base/strings/string_number_conversions.h"
     19 #include "base/strings/string_piece.h"
     20 #include "base/strings/string_util.h"
     21 #include "base/strings/stringprintf.h"
     22 #include "base/strings/utf_string_conversions.h"
     23 #include "base/synchronization/waitable_event.h"
     24 #include "base/time/time.h"
     25 #include "components/autofill/core/common/password_form.h"
     26 #include "components/password_manager/core/browser/psl_matching_helper.h"
     27 #include "content/public/browser/browser_thread.h"
     28 
     29 using autofill::PasswordForm;
     30 using base::UTF8ToUTF16;
     31 using base::UTF16ToUTF8;
     32 using content::BrowserThread;
     33 using password_manager::PSLMatchingHelper;
     34 
     35 #define GNOME_KEYRING_DEFINE_POINTER(name) \
     36   typeof(&::gnome_keyring_##name) GnomeKeyringLoader::gnome_keyring_##name;
     37 GNOME_KEYRING_FOR_EACH_FUNC(GNOME_KEYRING_DEFINE_POINTER)
     38 #undef GNOME_KEYRING_DEFINE_POINTER
     39 
     40 bool GnomeKeyringLoader::keyring_loaded = false;
     41 
     42 #if defined(DLOPEN_GNOME_KEYRING)
     43 
     44 #define GNOME_KEYRING_FUNCTION_INFO(name) \
     45   {"gnome_keyring_"#name, reinterpret_cast<void**>(&gnome_keyring_##name)},
     46 const GnomeKeyringLoader::FunctionInfo GnomeKeyringLoader::functions[] = {
     47   GNOME_KEYRING_FOR_EACH_FUNC(GNOME_KEYRING_FUNCTION_INFO)
     48   {NULL, NULL}
     49 };
     50 #undef GNOME_KEYRING_FUNCTION_INFO
     51 
     52 /* Load the library and initialize the function pointers. */
     53 bool GnomeKeyringLoader::LoadGnomeKeyring() {
     54   if (keyring_loaded)
     55     return true;
     56 
     57   void* handle = dlopen("libgnome-keyring.so.0", RTLD_NOW | RTLD_GLOBAL);
     58   if (!handle) {
     59     // We wanted to use GNOME Keyring, but we couldn't load it. Warn, because
     60     // either the user asked for this, or we autodetected it incorrectly. (Or
     61     // the system has broken libraries, which is also good to warn about.)
     62     LOG(WARNING) << "Could not load libgnome-keyring.so.0: " << dlerror();
     63     return false;
     64   }
     65 
     66   for (size_t i = 0; functions[i].name; ++i) {
     67     dlerror();
     68     *functions[i].pointer = dlsym(handle, functions[i].name);
     69     const char* error = dlerror();
     70     if (error) {
     71       LOG(ERROR) << "Unable to load symbol "
     72                  << functions[i].name << ": " << error;
     73       dlclose(handle);
     74       return false;
     75     }
     76   }
     77 
     78   keyring_loaded = true;
     79   // We leak the library handle. That's OK: this function is called only once.
     80   return true;
     81 }
     82 
     83 #else  // defined(DLOPEN_GNOME_KEYRING)
     84 
     85 bool GnomeKeyringLoader::LoadGnomeKeyring() {
     86   if (keyring_loaded)
     87     return true;
     88 #define GNOME_KEYRING_ASSIGN_POINTER(name) \
     89   gnome_keyring_##name = &::gnome_keyring_##name;
     90   GNOME_KEYRING_FOR_EACH_FUNC(GNOME_KEYRING_ASSIGN_POINTER)
     91 #undef GNOME_KEYRING_ASSIGN_POINTER
     92   keyring_loaded = true;
     93   return true;
     94 }
     95 
     96 #endif  // defined(DLOPEN_GNOME_KEYRING)
     97 
     98 namespace {
     99 
    100 const char kGnomeKeyringAppString[] = "chrome";
    101 
    102 // Convert the attributes of a given keyring entry into a new PasswordForm.
    103 // Note: does *not* get the actual password, as that is not a key attribute!
    104 // Returns NULL if the attributes are for the wrong application.
    105 scoped_ptr<PasswordForm> FormFromAttributes(GnomeKeyringAttributeList* attrs) {
    106   // Read the string and int attributes into the appropriate map.
    107   std::map<std::string, std::string> string_attr_map;
    108   std::map<std::string, uint32_t> uint_attr_map;
    109   for (guint i = 0; i < attrs->len; ++i) {
    110     GnomeKeyringAttribute attr = gnome_keyring_attribute_list_index(attrs, i);
    111     if (attr.type == GNOME_KEYRING_ATTRIBUTE_TYPE_STRING)
    112       string_attr_map[attr.name] = attr.value.string;
    113     else if (attr.type == GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32)
    114       uint_attr_map[attr.name] = attr.value.integer;
    115   }
    116   // Check to make sure this is a password we care about.
    117   const std::string& app_value = string_attr_map["application"];
    118   if (!base::StringPiece(app_value).starts_with(kGnomeKeyringAppString))
    119     return scoped_ptr<PasswordForm>();
    120 
    121   scoped_ptr<PasswordForm> form(new PasswordForm());
    122   form->origin = GURL(string_attr_map["origin_url"]);
    123   form->action = GURL(string_attr_map["action_url"]);
    124   form->username_element = UTF8ToUTF16(string_attr_map["username_element"]);
    125   form->username_value = UTF8ToUTF16(string_attr_map["username_value"]);
    126   form->password_element = UTF8ToUTF16(string_attr_map["password_element"]);
    127   form->submit_element = UTF8ToUTF16(string_attr_map["submit_element"]);
    128   form->signon_realm = string_attr_map["signon_realm"];
    129   form->ssl_valid = uint_attr_map["ssl_valid"];
    130   form->preferred = uint_attr_map["preferred"];
    131   int64 date_created = 0;
    132   bool date_ok = base::StringToInt64(string_attr_map["date_created"],
    133                                      &date_created);
    134   DCHECK(date_ok);
    135   form->date_created = base::Time::FromTimeT(date_created);
    136   form->blacklisted_by_user = uint_attr_map["blacklisted_by_user"];
    137   form->type = static_cast<PasswordForm::Type>(uint_attr_map["type"]);
    138   form->times_used = uint_attr_map["times_used"];
    139   form->scheme = static_cast<PasswordForm::Scheme>(uint_attr_map["scheme"]);
    140   int64 date_synced = 0;
    141   base::StringToInt64(string_attr_map["date_synced"], &date_synced);
    142   form->date_synced = base::Time::FromInternalValue(date_synced);
    143 
    144   return form.Pass();
    145 }
    146 
    147 // Parse all the results from the given GList into a PasswordFormList, and free
    148 // the GList. PasswordForms are allocated on the heap, and should be deleted by
    149 // the consumer. If not NULL, |lookup_form| is used to filter out results --
    150 // only credentials with signon realms passing the PSL matching (done by
    151 // |helper|) against |lookup_form->signon_realm| will be kept. PSL matched
    152 // results get their signon_realm, origin, and action rewritten to those of
    153 // |lookup_form_|, with the original signon_realm saved into the result's
    154 // original_signon_realm data member.
    155 void ConvertFormList(GList* found,
    156                      const PasswordForm* lookup_form,
    157                      const PSLMatchingHelper& helper,
    158                      NativeBackendGnome::PasswordFormList* forms) {
    159   PSLMatchingHelper::PSLDomainMatchMetric psl_domain_match_metric =
    160       PSLMatchingHelper::PSL_DOMAIN_MATCH_NONE;
    161   for (GList* element = g_list_first(found); element != NULL;
    162        element = g_list_next(element)) {
    163     GnomeKeyringFound* data = static_cast<GnomeKeyringFound*>(element->data);
    164     GnomeKeyringAttributeList* attrs = data->attributes;
    165 
    166     scoped_ptr<PasswordForm> form(FormFromAttributes(attrs));
    167     if (form) {
    168       if (lookup_form && form->signon_realm != lookup_form->signon_realm) {
    169         // This is not an exact match, we try PSL matching.
    170         if (lookup_form->scheme != PasswordForm::SCHEME_HTML ||
    171             form->scheme != PasswordForm::SCHEME_HTML ||
    172             !(PSLMatchingHelper::IsPublicSuffixDomainMatch(
    173                 lookup_form->signon_realm, form->signon_realm))) {
    174           continue;
    175         }
    176         psl_domain_match_metric = PSLMatchingHelper::PSL_DOMAIN_MATCH_FOUND;
    177         form->original_signon_realm = form->signon_realm;
    178         form->signon_realm = lookup_form->signon_realm;
    179         form->origin = lookup_form->origin;
    180         form->action = lookup_form->action;
    181       }
    182       if (data->secret) {
    183         form->password_value = UTF8ToUTF16(data->secret);
    184       } else {
    185         LOG(WARNING) << "Unable to access password from list element!";
    186       }
    187       forms->push_back(form.release());
    188     } else {
    189       LOG(WARNING) << "Could not initialize PasswordForm from attributes!";
    190     }
    191   }
    192   if (lookup_form) {
    193     UMA_HISTOGRAM_ENUMERATION(
    194         "PasswordManager.PslDomainMatchTriggering",
    195         helper.IsMatchingEnabled()
    196             ? psl_domain_match_metric
    197             : PSLMatchingHelper::PSL_DOMAIN_MATCH_DISABLED,
    198         PSLMatchingHelper::PSL_DOMAIN_MATCH_COUNT);
    199   }
    200 }
    201 
    202 // Schema is analagous to the fields in PasswordForm.
    203 // TODO(gcasto): Adding 'form_data' would be nice, but we would need to
    204 // serialize in a way that is guaranteed to not have any embedded NULLs. Pickle
    205 // doesn't make this guarantee, so we just don't serialize this field. Since
    206 // it's only used to crowd source data collection it doesn't matter that much
    207 // if it's not available on this platform.
    208 const GnomeKeyringPasswordSchema kGnomeSchema = {
    209   GNOME_KEYRING_ITEM_GENERIC_SECRET, {
    210     { "origin_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
    211     { "action_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
    212     { "username_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
    213     { "username_value", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
    214     { "password_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
    215     { "submit_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
    216     { "signon_realm", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
    217     { "ssl_valid", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 },
    218     { "preferred", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 },
    219     { "date_created", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
    220     { "blacklisted_by_user", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 },
    221     { "scheme", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 },
    222     { "type", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 },
    223     { "times_used", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 },
    224     { "date_synced", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
    225     // This field is always "chrome" so that we can search for it.
    226     { "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING },
    227     { NULL }
    228   }
    229 };
    230 
    231 // Sadly, PasswordStore goes to great lengths to switch from the originally
    232 // calling thread to the DB thread, and to provide an asynchronous API to
    233 // callers while using a synchronous (virtual) API provided by subclasses like
    234 // PasswordStoreX -- but GNOME Keyring really wants to be on the GLib main
    235 // thread, which is the UI thread to us. So we end up having to switch threads
    236 // again, possibly back to the very same thread (in case the UI thread is the
    237 // caller, e.g. in the password management UI), and *block* the DB thread
    238 // waiting for a response from the UI thread to provide the synchronous API
    239 // PasswordStore expects of us. (It will then in turn switch back to the
    240 // original caller to send the asynchronous reply to the original request.)
    241 
    242 // This class represents a call to a GNOME Keyring method. A RunnableMethod
    243 // should be posted to the UI thread to call one of its action methods, and then
    244 // a WaitResult() method should be called to wait for the result. Each instance
    245 // supports only one outstanding method at a time, though multiple instances may
    246 // be used in parallel.
    247 class GKRMethod : public GnomeKeyringLoader {
    248  public:
    249   typedef NativeBackendGnome::PasswordFormList PasswordFormList;
    250 
    251   GKRMethod() : event_(false, false), result_(GNOME_KEYRING_RESULT_CANCELLED) {}
    252 
    253   // Action methods. These call gnome_keyring_* functions. Call from UI thread.
    254   // See GetProfileSpecificAppString() for more information on the app string.
    255   void AddLogin(const PasswordForm& form, const char* app_string);
    256   void AddLoginSearch(const PasswordForm& form, const char* app_string);
    257   void UpdateLoginSearch(const PasswordForm& form, const char* app_string);
    258   void RemoveLogin(const PasswordForm& form, const char* app_string);
    259   void GetLogins(const PasswordForm& form, const char* app_string);
    260   void GetLoginsList(uint32_t blacklisted_by_user, const char* app_string);
    261   void GetAllLogins(const char* app_string);
    262 
    263   // Use after AddLogin, RemoveLogin.
    264   GnomeKeyringResult WaitResult();
    265 
    266   // Use after AddLoginSearch, UpdateLoginSearch, GetLogins, GetLoginsList,
    267   // GetAllLogins.
    268   GnomeKeyringResult WaitResult(PasswordFormList* forms);
    269 
    270  private:
    271   struct GnomeKeyringAttributeListFreeDeleter {
    272     inline void operator()(void* list) const {
    273       gnome_keyring_attribute_list_free(
    274           static_cast<GnomeKeyringAttributeList*>(list));
    275     }
    276   };
    277 
    278   typedef scoped_ptr<GnomeKeyringAttributeList,
    279                      GnomeKeyringAttributeListFreeDeleter> ScopedAttributeList;
    280 
    281   // Helper methods to abbreviate Gnome Keyring long API names.
    282   static void AppendString(ScopedAttributeList* list,
    283                            const char* name,
    284                            const char* value);
    285   static void AppendString(ScopedAttributeList* list,
    286                            const char* name,
    287                            const std::string& value);
    288   static void AppendUint32(ScopedAttributeList* list,
    289                            const char* name,
    290                            guint32 value);
    291 
    292   // All these callbacks are called on UI thread.
    293   static void OnOperationDone(GnomeKeyringResult result, gpointer data);
    294 
    295   static void OnOperationGetList(GnomeKeyringResult result, GList* list,
    296                                  gpointer data);
    297 
    298   base::WaitableEvent event_;
    299   GnomeKeyringResult result_;
    300   NativeBackendGnome::PasswordFormList forms_;
    301   // If the credential search is specified by a single form and needs to use PSL
    302   // matching, then the specifying form is stored in |lookup_form_|. If PSL
    303   // matching is used to find a result, then the results signon realm, origin
    304   // and action are stored are replaced by those of |lookup_form_|.
    305   // Additionally, |lookup_form_->signon_realm| is also used to narrow down the
    306   // found logins to those which indeed PSL-match the look-up. And finally,
    307   // |lookup_form_| set to NULL means that PSL matching is not required.
    308   scoped_ptr<PasswordForm> lookup_form_;
    309   const PSLMatchingHelper helper_;
    310 };
    311 
    312 void GKRMethod::AddLogin(const PasswordForm& form, const char* app_string) {
    313   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    314   time_t date_created = form.date_created.ToTimeT();
    315   // If we are asked to save a password with 0 date, use the current time.
    316   // We don't want to actually save passwords as though on January 1, 1970.
    317   if (!date_created)
    318     date_created = time(NULL);
    319   int64 date_synced = form.date_synced.ToInternalValue();
    320   gnome_keyring_store_password(
    321       &kGnomeSchema,
    322       NULL,  // Default keyring.
    323       form.origin.spec().c_str(),  // Display name.
    324       UTF16ToUTF8(form.password_value).c_str(),
    325       OnOperationDone,
    326       this,  // data
    327       NULL,  // destroy_data
    328       "origin_url", form.origin.spec().c_str(),
    329       "action_url", form.action.spec().c_str(),
    330       "username_element", UTF16ToUTF8(form.username_element).c_str(),
    331       "username_value", UTF16ToUTF8(form.username_value).c_str(),
    332       "password_element", UTF16ToUTF8(form.password_element).c_str(),
    333       "submit_element", UTF16ToUTF8(form.submit_element).c_str(),
    334       "signon_realm", form.signon_realm.c_str(),
    335       "ssl_valid", form.ssl_valid,
    336       "preferred", form.preferred,
    337       "date_created", base::Int64ToString(date_created).c_str(),
    338       "blacklisted_by_user", form.blacklisted_by_user,
    339       "type", form.type,
    340       "times_used", form.times_used,
    341       "scheme", form.scheme,
    342       "date_synced", base::Int64ToString(date_synced).c_str(),
    343       "application", app_string,
    344       NULL);
    345 }
    346 
    347 void GKRMethod::AddLoginSearch(const PasswordForm& form,
    348                                const char* app_string) {
    349   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    350   lookup_form_.reset(NULL);
    351   // Search GNOME Keyring for matching passwords to update.
    352   ScopedAttributeList attrs(gnome_keyring_attribute_list_new());
    353   AppendString(&attrs, "origin_url", form.origin.spec());
    354   AppendString(&attrs, "username_element", UTF16ToUTF8(form.username_element));
    355   AppendString(&attrs, "username_value", UTF16ToUTF8(form.username_value));
    356   AppendString(&attrs, "password_element", UTF16ToUTF8(form.password_element));
    357   AppendString(&attrs, "submit_element", UTF16ToUTF8(form.submit_element));
    358   AppendString(&attrs, "signon_realm", form.signon_realm);
    359   AppendString(&attrs, "application", app_string);
    360   gnome_keyring_find_items(GNOME_KEYRING_ITEM_GENERIC_SECRET,
    361                            attrs.get(),
    362                            OnOperationGetList,
    363                            /*data=*/this,
    364                            /*destroy_data=*/NULL);
    365 }
    366 
    367 void GKRMethod::UpdateLoginSearch(const PasswordForm& form,
    368                                   const char* app_string) {
    369   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    370   lookup_form_.reset(NULL);
    371   // Search GNOME Keyring for matching passwords to update.
    372   ScopedAttributeList attrs(gnome_keyring_attribute_list_new());
    373   AppendString(&attrs, "origin_url", form.origin.spec());
    374   AppendString(&attrs, "username_element", UTF16ToUTF8(form.username_element));
    375   AppendString(&attrs, "username_value", UTF16ToUTF8(form.username_value));
    376   AppendString(&attrs, "password_element", UTF16ToUTF8(form.password_element));
    377   AppendString(&attrs, "signon_realm", form.signon_realm);
    378   AppendString(&attrs, "application", app_string);
    379   gnome_keyring_find_items(GNOME_KEYRING_ITEM_GENERIC_SECRET,
    380                            attrs.get(),
    381                            OnOperationGetList,
    382                            /*data=*/this,
    383                            /*destroy_data=*/NULL);
    384 }
    385 
    386 void GKRMethod::RemoveLogin(const PasswordForm& form, const char* app_string) {
    387   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    388   // We find forms using the same fields as LoginDatabase::RemoveLogin().
    389   gnome_keyring_delete_password(
    390       &kGnomeSchema,
    391       OnOperationDone,
    392       this,  // data
    393       NULL,  // destroy_data
    394       "origin_url", form.origin.spec().c_str(),
    395       "action_url", form.action.spec().c_str(),
    396       "username_element", UTF16ToUTF8(form.username_element).c_str(),
    397       "username_value", UTF16ToUTF8(form.username_value).c_str(),
    398       "password_element", UTF16ToUTF8(form.password_element).c_str(),
    399       "submit_element", UTF16ToUTF8(form.submit_element).c_str(),
    400       "signon_realm", form.signon_realm.c_str(),
    401       "application", app_string,
    402       NULL);
    403 }
    404 
    405 void GKRMethod::GetLogins(const PasswordForm& form, const char* app_string) {
    406   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    407   lookup_form_.reset(new PasswordForm(form));
    408   // Search GNOME Keyring for matching passwords.
    409   ScopedAttributeList attrs(gnome_keyring_attribute_list_new());
    410   if (!helper_.ShouldPSLDomainMatchingApply(
    411            PSLMatchingHelper::GetRegistryControlledDomain(
    412                GURL(form.signon_realm)))) {
    413     AppendString(&attrs, "signon_realm", form.signon_realm);
    414   }
    415   AppendString(&attrs, "application", app_string);
    416   gnome_keyring_find_items(GNOME_KEYRING_ITEM_GENERIC_SECRET,
    417                            attrs.get(),
    418                            OnOperationGetList,
    419                            /*data=*/this,
    420                            /*destroy_data=*/NULL);
    421 }
    422 
    423 void GKRMethod::GetLoginsList(uint32_t blacklisted_by_user,
    424                               const char* app_string) {
    425   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    426   lookup_form_.reset(NULL);
    427   // Search GNOME Keyring for matching passwords.
    428   ScopedAttributeList attrs(gnome_keyring_attribute_list_new());
    429   AppendUint32(&attrs, "blacklisted_by_user", blacklisted_by_user);
    430   AppendString(&attrs, "application", app_string);
    431   gnome_keyring_find_items(GNOME_KEYRING_ITEM_GENERIC_SECRET,
    432                            attrs.get(),
    433                            OnOperationGetList,
    434                            /*data=*/this,
    435                            /*destroy_data=*/NULL);
    436 }
    437 
    438 void GKRMethod::GetAllLogins(const char* app_string) {
    439   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    440   lookup_form_.reset(NULL);
    441   // We need to search for something, otherwise we get no results - so
    442   // we search for the fixed application string.
    443   ScopedAttributeList attrs(gnome_keyring_attribute_list_new());
    444   AppendString(&attrs, "application", app_string);
    445   gnome_keyring_find_items(GNOME_KEYRING_ITEM_GENERIC_SECRET,
    446                            attrs.get(),
    447                            OnOperationGetList,
    448                            /*data=*/this,
    449                            /*destroy_data=*/NULL);
    450 }
    451 
    452 GnomeKeyringResult GKRMethod::WaitResult() {
    453   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    454   event_.Wait();
    455   return result_;
    456 }
    457 
    458 GnomeKeyringResult GKRMethod::WaitResult(PasswordFormList* forms) {
    459   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    460   event_.Wait();
    461   if (forms->empty()) {
    462     // Normal case. Avoid extra allocation by swapping.
    463     forms->swap(forms_);
    464   } else {
    465     // Rare case. Append forms_ to *forms.
    466     forms->insert(forms->end(), forms_.begin(), forms_.end());
    467     forms_.clear();
    468   }
    469   return result_;
    470 }
    471 
    472 // static
    473 void GKRMethod::AppendString(GKRMethod::ScopedAttributeList* list,
    474                              const char* name,
    475                              const char* value) {
    476   gnome_keyring_attribute_list_append_string(list->get(), name, value);
    477 }
    478 
    479 // static
    480 void GKRMethod::AppendString(GKRMethod::ScopedAttributeList* list,
    481                              const char* name,
    482                              const std::string& value) {
    483   AppendString(list, name, value.c_str());
    484 }
    485 
    486 // static
    487 void GKRMethod::AppendUint32(GKRMethod::ScopedAttributeList* list,
    488                              const char* name,
    489                              guint32 value) {
    490   gnome_keyring_attribute_list_append_uint32(list->get(), name, value);
    491 }
    492 
    493 // static
    494 void GKRMethod::OnOperationDone(GnomeKeyringResult result, gpointer data) {
    495   GKRMethod* method = static_cast<GKRMethod*>(data);
    496   method->result_ = result;
    497   method->event_.Signal();
    498 }
    499 
    500 // static
    501 void GKRMethod::OnOperationGetList(GnomeKeyringResult result, GList* list,
    502                                    gpointer data) {
    503   GKRMethod* method = static_cast<GKRMethod*>(data);
    504   method->result_ = result;
    505   method->forms_.clear();
    506   // |list| will be freed after this callback returns, so convert it now.
    507   ConvertFormList(
    508       list, method->lookup_form_.get(), method->helper_, &method->forms_);
    509   method->lookup_form_.reset(NULL);
    510   method->event_.Signal();
    511 }
    512 
    513 }  // namespace
    514 
    515 NativeBackendGnome::NativeBackendGnome(LocalProfileId id)
    516     : profile_id_(id) {
    517   app_string_ = GetProfileSpecificAppString();
    518 }
    519 
    520 NativeBackendGnome::~NativeBackendGnome() {
    521 }
    522 
    523 bool NativeBackendGnome::Init() {
    524   return LoadGnomeKeyring() && gnome_keyring_is_available();
    525 }
    526 
    527 bool NativeBackendGnome::RawAddLogin(const PasswordForm& form) {
    528   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    529   GKRMethod method;
    530   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    531                           base::Bind(&GKRMethod::AddLogin,
    532                                      base::Unretained(&method),
    533                                      form, app_string_.c_str()));
    534   GnomeKeyringResult result = method.WaitResult();
    535   if (result != GNOME_KEYRING_RESULT_OK) {
    536     LOG(ERROR) << "Keyring save failed: "
    537                << gnome_keyring_result_to_message(result);
    538     return false;
    539   }
    540   return true;
    541 }
    542 
    543 password_manager::PasswordStoreChangeList NativeBackendGnome::AddLogin(
    544     const PasswordForm& form) {
    545   // Based on LoginDatabase::AddLogin(), we search for an existing match based
    546   // on origin_url, username_element, username_value, password_element, submit
    547   // element, and signon_realm first, remove that, and then add the new entry.
    548   // We'd add the new one first, and then delete the original, but then the
    549   // delete might actually delete the newly-added entry!
    550   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    551   GKRMethod method;
    552   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    553                           base::Bind(&GKRMethod::AddLoginSearch,
    554                                      base::Unretained(&method),
    555                                      form, app_string_.c_str()));
    556   ScopedVector<autofill::PasswordForm> forms;
    557   GnomeKeyringResult result = method.WaitResult(&forms.get());
    558   if (result != GNOME_KEYRING_RESULT_OK &&
    559       result != GNOME_KEYRING_RESULT_NO_MATCH) {
    560     LOG(ERROR) << "Keyring find failed: "
    561                << gnome_keyring_result_to_message(result);
    562     return password_manager::PasswordStoreChangeList();
    563   }
    564   password_manager::PasswordStoreChangeList changes;
    565   if (forms.size() > 0) {
    566     if (forms.size() > 1) {
    567       LOG(WARNING) << "Adding login when there are " << forms.size()
    568                    << " matching logins already! Will replace only the first.";
    569     }
    570 
    571     if (RemoveLogin(*forms[0])) {
    572       changes.push_back(password_manager::PasswordStoreChange(
    573           password_manager::PasswordStoreChange::REMOVE, *forms[0]));
    574     }
    575   }
    576   if (RawAddLogin(form)) {
    577     changes.push_back(password_manager::PasswordStoreChange(
    578         password_manager::PasswordStoreChange::ADD, form));
    579   }
    580   return changes;
    581 }
    582 
    583 bool NativeBackendGnome::UpdateLogin(
    584     const PasswordForm& form,
    585     password_manager::PasswordStoreChangeList* changes) {
    586   // Based on LoginDatabase::UpdateLogin(), we search for forms to update by
    587   // origin_url, username_element, username_value, password_element, and
    588   // signon_realm. We then compare the result to the updated form. If they
    589   // differ in any of the mutable fields, then we remove the original, and
    590   // then add the new entry. We'd add the new one first, and then delete the
    591   // original, but then the delete might actually delete the newly-added entry!
    592   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    593   DCHECK(changes);
    594   changes->clear();
    595   GKRMethod method;
    596   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    597                           base::Bind(&GKRMethod::UpdateLoginSearch,
    598                                      base::Unretained(&method),
    599                                      form, app_string_.c_str()));
    600   ScopedVector<autofill::PasswordForm> forms;
    601   GnomeKeyringResult result = method.WaitResult(&forms.get());
    602   if (result != GNOME_KEYRING_RESULT_OK) {
    603     LOG(ERROR) << "Keyring find failed: "
    604                << gnome_keyring_result_to_message(result);
    605     return false;
    606   }
    607 
    608   bool removed = false;
    609   for (size_t i = 0; i < forms.size(); ++i) {
    610     if (*forms[i] != form) {
    611       RemoveLogin(*forms[i]);
    612       removed = true;
    613     }
    614   }
    615   if (!removed)
    616     return true;
    617 
    618   if (RawAddLogin(form)) {
    619     password_manager::PasswordStoreChange change(
    620         password_manager::PasswordStoreChange::UPDATE, form);
    621     changes->push_back(change);
    622     return true;
    623   }
    624   return false;
    625 }
    626 
    627 bool NativeBackendGnome::RemoveLogin(const PasswordForm& form) {
    628   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    629   GKRMethod method;
    630   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    631                           base::Bind(&GKRMethod::RemoveLogin,
    632                                      base::Unretained(&method),
    633                                      form, app_string_.c_str()));
    634   GnomeKeyringResult result = method.WaitResult();
    635   if (result != GNOME_KEYRING_RESULT_OK) {
    636     // Warning, not error, because this can sometimes happen due to the user
    637     // racing with the daemon to delete the password a second time.
    638     LOG(WARNING) << "Keyring delete failed: "
    639                  << gnome_keyring_result_to_message(result);
    640     return false;
    641   }
    642   return true;
    643 }
    644 
    645 bool NativeBackendGnome::RemoveLoginsCreatedBetween(
    646     base::Time delete_begin,
    647     base::Time delete_end,
    648     password_manager::PasswordStoreChangeList* changes) {
    649   return RemoveLoginsBetween(
    650       delete_begin, delete_end, CREATION_TIMESTAMP, changes);
    651 }
    652 
    653 bool NativeBackendGnome::RemoveLoginsSyncedBetween(
    654     base::Time delete_begin,
    655     base::Time delete_end,
    656     password_manager::PasswordStoreChangeList* changes) {
    657   return RemoveLoginsBetween(delete_begin, delete_end, SYNC_TIMESTAMP, changes);
    658 }
    659 
    660 bool NativeBackendGnome::GetLogins(const PasswordForm& form,
    661                                    PasswordFormList* forms) {
    662   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    663   GKRMethod method;
    664   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    665                           base::Bind(&GKRMethod::GetLogins,
    666                                      base::Unretained(&method),
    667                                      form, app_string_.c_str()));
    668   GnomeKeyringResult result = method.WaitResult(forms);
    669   if (result == GNOME_KEYRING_RESULT_NO_MATCH)
    670     return true;
    671   if (result != GNOME_KEYRING_RESULT_OK) {
    672     LOG(ERROR) << "Keyring find failed: "
    673                << gnome_keyring_result_to_message(result);
    674     return false;
    675   }
    676   return true;
    677 }
    678 
    679 bool NativeBackendGnome::GetAutofillableLogins(PasswordFormList* forms) {
    680   return GetLoginsList(forms, true);
    681 }
    682 
    683 bool NativeBackendGnome::GetBlacklistLogins(PasswordFormList* forms) {
    684   return GetLoginsList(forms, false);
    685 }
    686 
    687 bool NativeBackendGnome::GetLoginsList(PasswordFormList* forms,
    688                                        bool autofillable) {
    689   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    690 
    691   uint32_t blacklisted_by_user = !autofillable;
    692 
    693   GKRMethod method;
    694   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    695                           base::Bind(&GKRMethod::GetLoginsList,
    696                                      base::Unretained(&method),
    697                                      blacklisted_by_user, app_string_.c_str()));
    698   GnomeKeyringResult result = method.WaitResult(forms);
    699   if (result == GNOME_KEYRING_RESULT_NO_MATCH)
    700     return true;
    701   if (result != GNOME_KEYRING_RESULT_OK) {
    702     LOG(ERROR) << "Keyring find failed: "
    703                << gnome_keyring_result_to_message(result);
    704     return false;
    705   }
    706   return true;
    707 }
    708 
    709 bool NativeBackendGnome::GetAllLogins(PasswordFormList* forms) {
    710   GKRMethod method;
    711   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    712                           base::Bind(&GKRMethod::GetAllLogins,
    713                                      base::Unretained(&method),
    714                                      app_string_.c_str()));
    715   GnomeKeyringResult result = method.WaitResult(forms);
    716   if (result == GNOME_KEYRING_RESULT_NO_MATCH)
    717     return true;
    718   if (result != GNOME_KEYRING_RESULT_OK) {
    719     LOG(ERROR) << "Keyring find failed: "
    720                << gnome_keyring_result_to_message(result);
    721     return false;
    722   }
    723   return true;
    724 }
    725 
    726 bool NativeBackendGnome::GetLoginsBetween(base::Time get_begin,
    727                                           base::Time get_end,
    728                                           TimestampToCompare date_to_compare,
    729                                           PasswordFormList* forms) {
    730   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    731   // We could walk the list and add items as we find them, but it is much
    732   // easier to build the list and then filter the results.
    733   PasswordFormList all_forms;
    734   if (!GetAllLogins(&all_forms))
    735     return false;
    736 
    737   base::Time autofill::PasswordForm::*date_member =
    738       date_to_compare == CREATION_TIMESTAMP
    739           ? &autofill::PasswordForm::date_created
    740           : &autofill::PasswordForm::date_synced;
    741   for (size_t i = 0; i < all_forms.size(); ++i) {
    742     if (get_begin <= all_forms[i]->*date_member &&
    743         (get_end.is_null() || all_forms[i]->*date_member < get_end)) {
    744       forms->push_back(all_forms[i]);
    745     } else {
    746       delete all_forms[i];
    747     }
    748   }
    749 
    750   return true;
    751 }
    752 
    753 bool NativeBackendGnome::RemoveLoginsBetween(
    754     base::Time get_begin,
    755     base::Time get_end,
    756     TimestampToCompare date_to_compare,
    757     password_manager::PasswordStoreChangeList* changes) {
    758   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    759   DCHECK(changes);
    760   changes->clear();
    761   // We could walk the list and delete items as we find them, but it is much
    762   // easier to build the list and use RemoveLogin() to delete them.
    763   ScopedVector<autofill::PasswordForm> forms;
    764   if (!GetLoginsBetween(get_begin, get_end, date_to_compare, &forms.get()))
    765     return false;
    766 
    767   bool ok = true;
    768   for (size_t i = 0; i < forms.size(); ++i) {
    769     if (RemoveLogin(*forms[i])) {
    770       changes->push_back(password_manager::PasswordStoreChange(
    771           password_manager::PasswordStoreChange::REMOVE, *forms[i]));
    772     } else {
    773       ok = false;
    774     }
    775   }
    776   return ok;
    777 }
    778 
    779 std::string NativeBackendGnome::GetProfileSpecificAppString() const {
    780   // Originally, the application string was always just "chrome" and used only
    781   // so that we had *something* to search for since GNOME Keyring won't search
    782   // for nothing. Now we use it to distinguish passwords for different profiles.
    783   return base::StringPrintf("%s-%d", kGnomeKeyringAppString, profile_id_);
    784 }
    785