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