OLD | NEW |
(Empty) | |
| 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/proxy/chrome_keyring_proxy.h" |
| 6 |
| 7 // This file is part of a very small helper binary that runs without any |
| 8 // dependencies on base. Thus we can't use normal logging; we use assert. |
| 9 #include <assert.h> |
| 10 #include <errno.h> |
| 11 #include <glib.h> |
| 12 #include <glib-object.h> |
| 13 #include <gnome-keyring.h> |
| 14 #include <inttypes.h> |
| 15 #include <stdio.h> |
| 16 #include <stdlib.h> |
| 17 #include <string.h> |
| 18 |
| 19 #include <map> |
| 20 #include <string> |
| 21 #include <vector> |
| 22 |
| 23 #include "chrome/browser/password_manager/proxy/keyring_loader.h" |
| 24 #include "chrome/browser/password_manager/proxy/message_reader.h" |
| 25 |
| 26 namespace { |
| 27 |
| 28 // This is the approximation of PasswordForm that we store in |
| 29 // GNOME Keyring, using the schema in kGnomeSchema below. |
| 30 struct BasicPasswordForm { |
| 31 std::string origin; |
| 32 std::string action; |
| 33 std::string username_element; |
| 34 std::string username_value; |
| 35 std::string password_element; |
| 36 std::string password_value; |
| 37 std::string submit_element; |
| 38 std::string signon_realm; |
| 39 uint32_t ssl_valid; |
| 40 uint32_t preferred; |
| 41 std::string date_created; |
| 42 uint32_t blacklisted_by_user; |
| 43 uint32_t scheme; |
| 44 }; |
| 45 |
| 46 // Convert the attributes of a given keyring entry into a BasicPasswordForm. |
| 47 // Note: does *not* get the actual password, as that is not a key attribute! |
| 48 void FormFromAttributes(GnomeKeyringAttributeList* attrs, |
| 49 BasicPasswordForm* form) { |
| 50 // Read the string and int attributes into the appropriate map. |
| 51 std::map<std::string, std::string> string_attr_map; |
| 52 std::map<std::string, uint32_t> uint_attr_map; |
| 53 for (guint i = 0; i < attrs->len; ++i) { |
| 54 GnomeKeyringAttribute attr = gnome_keyring_attribute_list_index(attrs, i); |
| 55 if (attr.type == GNOME_KEYRING_ATTRIBUTE_TYPE_STRING) |
| 56 string_attr_map[attr.name] = attr.value.string; |
| 57 else if (attr.type == GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32) |
| 58 uint_attr_map[attr.name] = attr.value.integer; |
| 59 } |
| 60 |
| 61 form->origin = string_attr_map["origin_url"]; |
| 62 form->action = string_attr_map["action_url"]; |
| 63 form->username_element = string_attr_map["username_element"]; |
| 64 form->username_value = string_attr_map["username_value"]; |
| 65 form->password_element = string_attr_map["password_element"]; |
| 66 form->submit_element = string_attr_map["submit_element"]; |
| 67 form->signon_realm = string_attr_map["signon_realm"]; |
| 68 form->ssl_valid = uint_attr_map["ssl_valid"]; |
| 69 form->preferred = uint_attr_map["preferred"]; |
| 70 form->date_created = string_attr_map["date_created"]; |
| 71 form->blacklisted_by_user = uint_attr_map["blacklisted_by_user"]; |
| 72 form->scheme = uint_attr_map["scheme"]; |
| 73 } |
| 74 |
| 75 // Parse all the results from the given GList into a list of BasicPasswordForms. |
| 76 void ConvertFormList(GList* found, std::vector<BasicPasswordForm>* forms) { |
| 77 GList* element = g_list_first(found); |
| 78 while (element != NULL) { |
| 79 GnomeKeyringFound* data = static_cast<GnomeKeyringFound*>(element->data); |
| 80 GnomeKeyringAttributeList* attrs = data->attributes; |
| 81 |
| 82 forms->resize(forms->size() + 1); |
| 83 FormFromAttributes(attrs, &forms->back()); |
| 84 if (data->secret) |
| 85 forms->back().password_value = data->secret; |
| 86 |
| 87 element = g_list_next(element); |
| 88 } |
| 89 } |
| 90 |
| 91 // Schema is analagous to the fields in BasicPasswordForm. |
| 92 const GnomeKeyringPasswordSchema kGnomeSchema = { |
| 93 GNOME_KEYRING_ITEM_GENERIC_SECRET, { |
| 94 { "origin_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, |
| 95 { "action_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, |
| 96 { "username_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, |
| 97 { "username_value", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, |
| 98 { "password_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, |
| 99 { "submit_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, |
| 100 { "signon_realm", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, |
| 101 { "ssl_valid", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 }, |
| 102 { "preferred", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 }, |
| 103 { "date_created", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, |
| 104 { "blacklisted_by_user", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 }, |
| 105 { "scheme", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 }, |
| 106 // This field is used to disambiguate passwords for different profiles. |
| 107 { "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, |
| 108 { NULL } |
| 109 } |
| 110 }; |
| 111 |
| 112 // A simple class that encapsulates the boilerplate needed to tell |
| 113 // GLib how to watch a file descriptor for reading. |
| 114 class FDReadSource : public GSource { |
| 115 public: |
| 116 static GSource* Allocate(int fd) { |
| 117 GSource* source = g_source_new(&functions, sizeof(FDReadSource)); |
| 118 FDReadSource* fdsource = static_cast<FDReadSource*>(source); |
| 119 fdsource->pollfd.fd = fd; |
| 120 fdsource->pollfd.events = G_IO_IN | G_IO_HUP | G_IO_ERR; |
| 121 g_source_add_poll(source, &fdsource->pollfd); |
| 122 return source; |
| 123 } |
| 124 |
| 125 private: |
| 126 // We don't allocate or free FDReadSource ourselves. Instead, g_source_new() |
| 127 // will do that for us. So, make all the constructors/destructors private. |
| 128 FDReadSource(); |
| 129 FDReadSource(const FDReadSource&); |
| 130 void operator=(const FDReadSource&); |
| 131 ~FDReadSource(); |
| 132 |
| 133 static gboolean Prepare(GSource* source, gint* timeout) { |
| 134 *timeout = -1; |
| 135 return FALSE; |
| 136 } |
| 137 |
| 138 static gboolean Check(GSource* source) { |
| 139 FDReadSource* fdsource = static_cast<FDReadSource*>(source); |
| 140 return !!(fdsource->pollfd.revents & (G_IO_IN | G_IO_HUP | G_IO_ERR)); |
| 141 } |
| 142 |
| 143 static gboolean Dispatch(GSource* source, |
| 144 GSourceFunc callback, |
| 145 gpointer user_data) { |
| 146 if (!callback) |
| 147 return FALSE; // No callback? Remove the source so it doesn't spin. |
| 148 return callback(user_data); |
| 149 } |
| 150 |
| 151 static GSourceFuncs functions; |
| 152 |
| 153 GPollFD pollfd; |
| 154 }; |
| 155 |
| 156 GSourceFuncs FDReadSource::functions = { |
| 157 Prepare, Check, Dispatch, NULL // .finalize |
| 158 }; |
| 159 |
| 160 } // anonymous namespace |
| 161 |
| 162 class MessageProcessor : public GnomeKeyringLoader { |
| 163 public: |
| 164 explicit MessageProcessor(FILE* output) : output_(output) {} |
| 165 |
| 166 bool Process(const std::vector<std::string>& lines) { |
| 167 if (lines.size() < 1) { |
| 168 // Although an empty message is technically an error, we choose to be |
| 169 // lenient and ignore it. It makes manual testing easier. |
| 170 return true; |
| 171 } |
| 172 if (lines[0].length() < 2) { |
| 173 // The first line must have a command character followed by an id. |
| 174 return false; |
| 175 } |
| 176 int id = atoi(&lines[0].c_str()[1]); |
| 177 if (id <= 0) { |
| 178 // The id must be at least 1. |
| 179 return false; |
| 180 } |
| 181 if (lines[0][0] == ChromeKeyringProxyCommands::kAddLogin) { |
| 182 // Store a password. (AddLogin) |
| 183 if (lines.size() != 16) |
| 184 return false; |
| 185 Request* request = AllocateRequest(id); |
| 186 if (!request) |
| 187 return false; |
| 188 gnome_keyring_store_password( |
| 189 &kGnomeSchema, |
| 190 NULL, // Default keyring. |
| 191 &lines[1].c_str()[1], // Display name. |
| 192 &lines[2].c_str()[1], // Password. |
| 193 OnOperationDone, |
| 194 request, // data |
| 195 NULL, // destroy_data |
| 196 "origin_url", &lines[3].c_str()[1], |
| 197 "action_url", &lines[4].c_str()[1], |
| 198 "username_element", &lines[5].c_str()[1], |
| 199 "username_value", &lines[6].c_str()[1], |
| 200 "password_element", &lines[7].c_str()[1], |
| 201 "submit_element", &lines[8].c_str()[1], |
| 202 "signon_realm", &lines[9].c_str()[1], |
| 203 "ssl_valid", lines[10] != "0", |
| 204 "preferred", lines[11] != "0", |
| 205 "date_created", &lines[12].c_str()[1], |
| 206 "blacklisted_by_user", lines[13] != "0", |
| 207 "scheme", atoi(lines[14].c_str()), |
| 208 "application", lines[15].c_str(), |
| 209 NULL); |
| 210 } else if (lines[0][0] == ChromeKeyringProxyCommands::kRemoveLogin) { |
| 211 // Delete a password. (RemoveLogin) |
| 212 if (lines.size() != 9) |
| 213 return false; |
| 214 Request* request = AllocateRequest(id); |
| 215 if (!request) |
| 216 return false; |
| 217 gnome_keyring_delete_password( |
| 218 &kGnomeSchema, |
| 219 OnOperationDone, |
| 220 request, // data |
| 221 NULL, // destroy_data |
| 222 "origin_url", &lines[1].c_str()[1], |
| 223 "action_url", &lines[2].c_str()[1], |
| 224 "username_element", &lines[3].c_str()[1], |
| 225 "username_value", &lines[4].c_str()[1], |
| 226 "password_element", &lines[5].c_str()[1], |
| 227 "submit_element", &lines[6].c_str()[1], |
| 228 "signon_realm", &lines[7].c_str()[1], |
| 229 "application", lines[8].c_str(), |
| 230 NULL); |
| 231 } else if (lines[0][0] == ChromeKeyringProxyCommands::kAddLoginSearch) { |
| 232 // Find passwords before adding a new one. (AddLoginSearch) |
| 233 if (lines.size() != 8) |
| 234 return false; |
| 235 Request* request = AllocateRequest(id); |
| 236 if (!request) |
| 237 return false; |
| 238 gnome_keyring_find_itemsv( |
| 239 GNOME_KEYRING_ITEM_GENERIC_SECRET, |
| 240 OnFindDone, |
| 241 request, // data |
| 242 NULL, // destroy_data |
| 243 "origin_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, |
| 244 &lines[1].c_str()[1], |
| 245 "username_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, |
| 246 &lines[2].c_str()[1], |
| 247 "username_value", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, |
| 248 &lines[3].c_str()[1], |
| 249 "password_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, |
| 250 &lines[4].c_str()[1], |
| 251 "submit_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, |
| 252 &lines[5].c_str()[1], |
| 253 "signon_realm", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, |
| 254 &lines[6].c_str()[1], |
| 255 "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, |
| 256 lines[7].c_str(), |
| 257 NULL); |
| 258 } else if (lines[0][0] == ChromeKeyringProxyCommands::kUpdateLoginSearch) { |
| 259 // Find passwords to update. (UpdateLoginSearch) |
| 260 if (lines.size() != 7) |
| 261 return false; |
| 262 Request* request = AllocateRequest(id); |
| 263 if (!request) |
| 264 return false; |
| 265 gnome_keyring_find_itemsv( |
| 266 GNOME_KEYRING_ITEM_GENERIC_SECRET, |
| 267 OnFindDone, |
| 268 request, // data |
| 269 NULL, // destroy_data |
| 270 "origin_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, |
| 271 &lines[1].c_str()[1], |
| 272 "username_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, |
| 273 &lines[2].c_str()[1], |
| 274 "username_value", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, |
| 275 &lines[3].c_str()[1], |
| 276 "password_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, |
| 277 &lines[4].c_str()[1], |
| 278 "signon_realm", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, |
| 279 &lines[5].c_str()[1], |
| 280 "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, |
| 281 lines[6].c_str(), |
| 282 NULL); |
| 283 } else if (lines[0][0] == ChromeKeyringProxyCommands::kGetLogins) { |
| 284 // Find passwords to autofill. (GetLogins) |
| 285 if (lines.size() != 3) |
| 286 return false; |
| 287 Request* request = AllocateRequest(id); |
| 288 if (!request) |
| 289 return false; |
| 290 gnome_keyring_find_itemsv( |
| 291 GNOME_KEYRING_ITEM_GENERIC_SECRET, |
| 292 OnFindDone, |
| 293 request, // data |
| 294 NULL, // destroy_data |
| 295 "signon_realm", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, |
| 296 &lines[1].c_str()[1], |
| 297 "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, |
| 298 lines[2].c_str(), |
| 299 NULL); |
| 300 } else if (lines[0][0] == ChromeKeyringProxyCommands::kGetLoginsList) { |
| 301 // Find passwords to show in the password manager. (GetLoginsList) |
| 302 if (lines.size() != 3) |
| 303 return false; |
| 304 Request* request = AllocateRequest(id); |
| 305 if (!request) |
| 306 return false; |
| 307 gnome_keyring_find_itemsv( |
| 308 GNOME_KEYRING_ITEM_GENERIC_SECRET, |
| 309 OnFindDone, |
| 310 request, // data |
| 311 NULL, // destroy_data |
| 312 "blacklisted_by_user", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32, |
| 313 lines[1] != "0", |
| 314 "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, |
| 315 lines[2].c_str(), |
| 316 NULL); |
| 317 } else if (lines[0][0] == ChromeKeyringProxyCommands::kGetAllLogins) { |
| 318 // Find all passwords. (GetAllLogins) |
| 319 if (lines.size() != 2) |
| 320 return false; |
| 321 Request* request = AllocateRequest(id); |
| 322 if (!request) |
| 323 return false; |
| 324 // We need to search for something, otherwise we get no results - so |
| 325 // we search for the fixed application string. |
| 326 gnome_keyring_find_itemsv( |
| 327 GNOME_KEYRING_ITEM_GENERIC_SECRET, |
| 328 OnFindDone, |
| 329 request, // data |
| 330 NULL, // destroy_data |
| 331 "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, |
| 332 lines[1].c_str(), |
| 333 NULL); |
| 334 } else { |
| 335 // Unexpected command line. |
| 336 return false; |
| 337 } |
| 338 return true; |
| 339 } |
| 340 |
| 341 private: |
| 342 struct Request { |
| 343 int id; |
| 344 MessageProcessor* processor; |
| 345 |
| 346 Request(int id, MessageProcessor* processor) |
| 347 : id(id), processor(processor) { |
| 348 } |
| 349 }; |
| 350 |
| 351 Request* AllocateRequest(int id) { |
| 352 std::pair<std::map<int, Request>::iterator, bool> result = |
| 353 requests_.insert(std::make_pair(id, Request(id, this))); |
| 354 return result.second ? &result.first->second : NULL; |
| 355 } |
| 356 |
| 357 void ReplyAndFreeRequest(Request* request, |
| 358 const std::vector<std::string>& reply) { |
| 359 for (size_t i = 0; i < reply.size(); ++i) |
| 360 fprintf(output_, "%s\n", reply[i].c_str()); |
| 361 fprintf(output_, "\n"); |
| 362 fflush(output_); |
| 363 requests_.erase(request->id); |
| 364 } |
| 365 |
| 366 static void OnOperationDone(GnomeKeyringResult result, gpointer data) { |
| 367 Request* request = static_cast<Request*>(data); |
| 368 std::vector<std::string> reply; |
| 369 char line[32]; |
| 370 snprintf(line, sizeof(line), "%d %d", request->id, result); |
| 371 reply.push_back(line); |
| 372 request->processor->ReplyAndFreeRequest(request, reply); |
| 373 } |
| 374 |
| 375 static void OnFindDone(GnomeKeyringResult result, |
| 376 GList* list, gpointer data) { |
| 377 Request* request = static_cast<Request*>(data); |
| 378 std::vector<std::string> reply; |
| 379 char line[32]; |
| 380 snprintf(line, sizeof(line), "%d %d", request->id, result); |
| 381 reply.push_back(line); |
| 382 // |list| will be freed after this callback returns, so convert it now. |
| 383 std::vector<BasicPasswordForm> forms; |
| 384 ConvertFormList(list, &forms); |
| 385 for (size_t i = 0; i < forms.size(); ++i) { |
| 386 reply.push_back("+" + forms[i].origin); |
| 387 reply.push_back("+" + forms[i].action); |
| 388 reply.push_back("+" + forms[i].username_element); |
| 389 reply.push_back("+" + forms[i].username_value); |
| 390 reply.push_back("+" + forms[i].password_element); |
| 391 reply.push_back("+" + forms[i].password_value); |
| 392 reply.push_back("+" + forms[i].submit_element); |
| 393 reply.push_back("+" + forms[i].signon_realm); |
| 394 reply.push_back(forms[i].ssl_valid ? "1" : "0"); |
| 395 reply.push_back(forms[i].preferred ? "1" : "0"); |
| 396 reply.push_back("+" + forms[i].date_created); |
| 397 reply.push_back(forms[i].blacklisted_by_user ? "1" : "0"); |
| 398 snprintf(line, sizeof(line), "%u", forms[i].scheme); |
| 399 reply.push_back(line); |
| 400 } |
| 401 request->processor->ReplyAndFreeRequest(request, reply); |
| 402 } |
| 403 |
| 404 FILE* output_; |
| 405 std::map<int, Request> requests_; |
| 406 }; |
| 407 |
| 408 ChromeKeyringProxy::ChromeKeyringProxy(int fd, FILE* output) |
| 409 : fd_(fd), processor_(new MessageProcessor(output)) { |
| 410 main_loop_ = g_main_loop_new(NULL, FALSE); // is_running |
| 411 input_ = FDReadSource::Allocate(fd_); |
| 412 g_source_set_callback(input_, DataReady, this, NULL); |
| 413 g_source_attach(input_, NULL); |
| 414 } |
| 415 |
| 416 ChromeKeyringProxy::~ChromeKeyringProxy() { |
| 417 g_source_remove_by_user_data(this); |
| 418 g_source_unref(input_); |
| 419 g_main_loop_unref(main_loop_); |
| 420 } |
| 421 |
| 422 bool ChromeKeyringProxy::Init() { |
| 423 return LoadGnomeKeyring() && gnome_keyring_is_available(); |
| 424 } |
| 425 |
| 426 void ChromeKeyringProxy::Run() { |
| 427 g_main_loop_run(main_loop_); |
| 428 } |
| 429 |
| 430 void ChromeKeyringProxy::Stop() { |
| 431 if (!g_main_loop_is_running(main_loop_)) |
| 432 fprintf(stderr, "%s:%d: main loop not running!\n", __FILE__, __LINE__); |
| 433 g_main_loop_quit(main_loop_); |
| 434 } |
| 435 |
| 436 void ChromeKeyringProxy::ReadData() { |
| 437 // We don't need to read all the available data. If there is still data left |
| 438 // after we return, then we'll get called again because the descriptor will |
| 439 // still be ready to read. So just read a fixed amount, for simplicity. |
| 440 char buffer[256]; |
| 441 ssize_t available = read(fd_, buffer, sizeof(buffer)); |
| 442 if (available > 0) { |
| 443 // Got some data. Handle it. Note that this may not be a complete message. |
| 444 ssize_t used = 0; |
| 445 while (used < available) { |
| 446 ssize_t handled = reader_.HandleData(&buffer[used], available); |
| 447 assert(handled > 0); |
| 448 used += handled; |
| 449 available -= handled; |
| 450 if (reader_.is_complete()) { |
| 451 if (!processor_->Process(reader_.lines())) { |
| 452 // A fatal processing error occurred. Shut down the proxy. |
| 453 Stop(); |
| 454 } |
| 455 reader_.Reset(); |
| 456 } else { |
| 457 assert(available == 0); |
| 458 } |
| 459 } |
| 460 } else if (available == 0 || errno != EINTR) { |
| 461 // EOF, or an unexpected error. Shut down the proxy. |
| 462 Stop(); |
| 463 } |
| 464 } |
| 465 |
| 466 // static |
| 467 gboolean ChromeKeyringProxy::DataReady(gpointer data) { |
| 468 static_cast<ChromeKeyringProxy*>(data)->ReadData(); |
| 469 // We always keep this source. If we run out of input, we should quit. |
| 470 return TRUE; |
| 471 } |
OLD | NEW |