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