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