|
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/keyring_proxy/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/keyring_proxy/gnome_keyring_loader.h" | |
24 #include "chrome/browser/password_manager/keyring_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. | |
vandebo (ex-Chrome)
2011/12/01 19:57:30
Would it make sense to put the attribute index in
| |
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 }, | |
vandebo (ex-Chrome)
2011/12/01 19:57:30
The horse has left the barn, but if you had used a
| |
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 namespace keyring_proxy { | |
163 | |
164 class MessageProcessor : public GnomeKeyringLoader { | |
165 public: | |
166 explicit MessageProcessor(FILE* output) : output_(output) {} | |
167 | |
168 bool Process(const std::vector<std::string>& lines) { | |
169 if (lines.size() < 1) { | |
170 // Although an empty message is technically an error, we choose to be | |
171 // lenient and ignore it. It makes manual testing easier. | |
172 return true; | |
173 } | |
174 if (lines[0].length() < 2) { | |
175 // The first line must have a command character followed by an id. | |
176 return false; | |
177 } | |
178 int id = atoi(&lines[0].c_str()[1]); | |
179 if (id <= 0) { | |
180 // The id must be at least 1. | |
181 return false; | |
182 } | |
183 if (lines[0][0] == KeyringProxy::kAddLoginCommand) { | |
vandebo (ex-Chrome)
2011/12/01 19:57:30
switch() on lines[0][0] ?
| |
184 // Store a password. (AddLogin) | |
185 if (lines.size() != 16) | |
186 return false; | |
187 Request* request = AllocateRequest(id); | |
188 if (!request) | |
189 return false; | |
190 gnome_keyring_store_password( | |
191 &kGnomeSchema, | |
192 NULL, // Default keyring. | |
193 &lines[1].c_str()[1], // Display name. | |
194 &lines[2].c_str()[1], // Password. | |
195 OnOperationDone, | |
196 request, // data | |
197 NULL, // destroy_data | |
198 "origin_url", &lines[3].c_str()[1], | |
199 "action_url", &lines[4].c_str()[1], | |
200 "username_element", &lines[5].c_str()[1], | |
201 "username_value", &lines[6].c_str()[1], | |
202 "password_element", &lines[7].c_str()[1], | |
203 "submit_element", &lines[8].c_str()[1], | |
204 "signon_realm", &lines[9].c_str()[1], | |
205 "ssl_valid", lines[10] != "0", | |
206 "preferred", lines[11] != "0", | |
207 "date_created", &lines[12].c_str()[1], | |
208 "blacklisted_by_user", lines[13] != "0", | |
209 "scheme", atoi(lines[14].c_str()), | |
210 "application", lines[15].c_str(), | |
211 NULL); | |
212 } else if (lines[0][0] == KeyringProxy::kRemoveLoginCommand) { | |
213 // Delete a password. (RemoveLogin) | |
214 if (lines.size() != 9) | |
215 return false; | |
216 Request* request = AllocateRequest(id); | |
217 if (!request) | |
218 return false; | |
219 gnome_keyring_delete_password( | |
220 &kGnomeSchema, | |
221 OnOperationDone, | |
222 request, // data | |
223 NULL, // destroy_data | |
224 "origin_url", &lines[1].c_str()[1], | |
225 "action_url", &lines[2].c_str()[1], | |
226 "username_element", &lines[3].c_str()[1], | |
227 "username_value", &lines[4].c_str()[1], | |
228 "password_element", &lines[5].c_str()[1], | |
229 "submit_element", &lines[6].c_str()[1], | |
230 "signon_realm", &lines[7].c_str()[1], | |
231 "application", lines[8].c_str(), | |
232 NULL); | |
233 } else if (lines[0][0] == KeyringProxy::kAddLoginSearchCommand) { | |
234 // Find passwords before adding a new one. (AddLoginSearch) | |
235 if (lines.size() != 8) | |
236 return false; | |
237 Request* request = AllocateRequest(id); | |
238 if (!request) | |
239 return false; | |
240 gnome_keyring_find_itemsv( | |
241 GNOME_KEYRING_ITEM_GENERIC_SECRET, | |
242 OnFindDone, | |
243 request, // data | |
244 NULL, // destroy_data | |
245 "origin_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, | |
246 &lines[1].c_str()[1], | |
247 "username_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, | |
248 &lines[2].c_str()[1], | |
249 "username_value", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, | |
250 &lines[3].c_str()[1], | |
251 "password_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, | |
252 &lines[4].c_str()[1], | |
253 "submit_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, | |
254 &lines[5].c_str()[1], | |
255 "signon_realm", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, | |
256 &lines[6].c_str()[1], | |
257 "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, | |
258 lines[7].c_str(), | |
259 NULL); | |
260 } else if (lines[0][0] == KeyringProxy::kUpdateLoginSearchCommand) { | |
261 // Find passwords to update. (UpdateLoginSearch) | |
262 if (lines.size() != 7) | |
263 return false; | |
264 Request* request = AllocateRequest(id); | |
265 if (!request) | |
266 return false; | |
267 gnome_keyring_find_itemsv( | |
268 GNOME_KEYRING_ITEM_GENERIC_SECRET, | |
269 OnFindDone, | |
270 request, // data | |
271 NULL, // destroy_data | |
272 "origin_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, | |
273 &lines[1].c_str()[1], | |
274 "username_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, | |
275 &lines[2].c_str()[1], | |
276 "username_value", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, | |
277 &lines[3].c_str()[1], | |
278 "password_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, | |
279 &lines[4].c_str()[1], | |
280 "signon_realm", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, | |
281 &lines[5].c_str()[1], | |
282 "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, | |
283 lines[6].c_str(), | |
284 NULL); | |
285 } else if (lines[0][0] == KeyringProxy::kGetLoginsCommand) { | |
286 // Find passwords to autofill. (GetLogins) | |
287 if (lines.size() != 3) | |
288 return false; | |
289 Request* request = AllocateRequest(id); | |
290 if (!request) | |
291 return false; | |
292 gnome_keyring_find_itemsv( | |
293 GNOME_KEYRING_ITEM_GENERIC_SECRET, | |
294 OnFindDone, | |
295 request, // data | |
296 NULL, // destroy_data | |
297 "signon_realm", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, | |
298 &lines[1].c_str()[1], | |
299 "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, | |
300 lines[2].c_str(), | |
301 NULL); | |
302 } else if (lines[0][0] == KeyringProxy::kGetLoginsListCommand) { | |
303 // Find passwords to show in the password manager. (GetLoginsList) | |
304 if (lines.size() != 3) | |
305 return false; | |
306 Request* request = AllocateRequest(id); | |
307 if (!request) | |
308 return false; | |
309 gnome_keyring_find_itemsv( | |
310 GNOME_KEYRING_ITEM_GENERIC_SECRET, | |
311 OnFindDone, | |
312 request, // data | |
313 NULL, // destroy_data | |
314 "blacklisted_by_user", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32, | |
315 lines[1] != "0", | |
316 "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, | |
317 lines[2].c_str(), | |
318 NULL); | |
319 } else if (lines[0][0] == KeyringProxy::kGetAllLoginsCommand) { | |
320 // Find all passwords. (GetAllLogins) | |
321 if (lines.size() != 2) | |
322 return false; | |
323 Request* request = AllocateRequest(id); | |
324 if (!request) | |
325 return false; | |
326 // We need to search for something, otherwise we get no results - so | |
327 // we search for the fixed application string. | |
328 gnome_keyring_find_itemsv( | |
329 GNOME_KEYRING_ITEM_GENERIC_SECRET, | |
330 OnFindDone, | |
331 request, // data | |
332 NULL, // destroy_data | |
333 "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, | |
334 lines[1].c_str(), | |
335 NULL); | |
336 } else { | |
337 // Unexpected command line. | |
338 return false; | |
339 } | |
340 return true; | |
341 } | |
342 | |
343 private: | |
344 struct Request { | |
345 int id; | |
346 MessageProcessor* processor; | |
347 | |
348 Request(int id, MessageProcessor* processor) | |
349 : id(id), processor(processor) { | |
350 } | |
351 }; | |
352 | |
353 Request* AllocateRequest(int id) { | |
354 std::pair<std::map<int, Request>::iterator, bool> result = | |
355 requests_.insert(std::make_pair(id, Request(id, this))); | |
356 return result.second ? &result.first->second : NULL; | |
357 } | |
358 | |
359 void ReplyAndFreeRequest(Request* request, | |
360 const std::vector<std::string>& reply) { | |
361 for (size_t i = 0; i < reply.size(); ++i) | |
362 fprintf(output_, "%s\n", reply[i].c_str()); | |
363 fprintf(output_, "\n"); | |
364 fflush(output_); | |
365 requests_.erase(request->id); | |
366 } | |
367 | |
368 static void OnOperationDone(GnomeKeyringResult result, gpointer data) { | |
369 Request* request = static_cast<Request*>(data); | |
370 std::vector<std::string> reply; | |
371 char line[32]; | |
372 snprintf(line, sizeof(line), "%d %d", request->id, result); | |
373 reply.push_back(line); | |
374 request->processor->ReplyAndFreeRequest(request, reply); | |
375 } | |
376 | |
377 static void OnFindDone(GnomeKeyringResult result, | |
378 GList* list, gpointer data) { | |
379 Request* request = static_cast<Request*>(data); | |
380 std::vector<std::string> reply; | |
381 char line[32]; | |
382 snprintf(line, sizeof(line), "%d %d", request->id, result); | |
383 reply.push_back(line); | |
384 // |list| will be freed after this callback returns, so convert it now. | |
vandebo (ex-Chrome)
2011/12/01 19:57:30
I think this comment no longer applies?
| |
385 std::vector<BasicPasswordForm> forms; | |
386 ConvertFormList(list, &forms); | |
387 for (size_t i = 0; i < forms.size(); ++i) { | |
388 reply.push_back("+" + forms[i].origin); | |
389 reply.push_back("+" + forms[i].action); | |
390 reply.push_back("+" + forms[i].username_element); | |
391 reply.push_back("+" + forms[i].username_value); | |
392 reply.push_back("+" + forms[i].password_element); | |
393 reply.push_back("+" + forms[i].password_value); | |
394 reply.push_back("+" + forms[i].submit_element); | |
395 reply.push_back("+" + forms[i].signon_realm); | |
396 reply.push_back(forms[i].ssl_valid ? "1" : "0"); | |
397 reply.push_back(forms[i].preferred ? "1" : "0"); | |
398 reply.push_back("+" + forms[i].date_created); | |
399 reply.push_back(forms[i].blacklisted_by_user ? "1" : "0"); | |
400 snprintf(line, sizeof(line), "%u", forms[i].scheme); | |
401 reply.push_back(line); | |
402 } | |
403 request->processor->ReplyAndFreeRequest(request, reply); | |
404 } | |
405 | |
406 FILE* output_; | |
407 std::map<int, Request> requests_; | |
408 }; | |
409 | |
410 KeyringProxy::KeyringProxy(int fd, FILE* output) | |
411 : fd_(fd), processor_(new MessageProcessor(output)) { | |
412 main_loop_ = g_main_loop_new(NULL, FALSE); // is_running | |
413 input_ = FDReadSource::Allocate(fd_); | |
414 g_source_set_callback(input_, DataReady, this, NULL); | |
415 g_source_attach(input_, NULL); | |
416 } | |
417 | |
418 KeyringProxy::~KeyringProxy() { | |
419 g_source_remove_by_user_data(this); | |
420 g_source_unref(input_); | |
421 g_main_loop_unref(main_loop_); | |
422 } | |
423 | |
424 bool KeyringProxy::Init() { | |
425 return LoadGnomeKeyring() && gnome_keyring_is_available(); | |
426 } | |
427 | |
428 void KeyringProxy::Run() { | |
429 g_main_loop_run(main_loop_); | |
430 } | |
431 | |
432 void KeyringProxy::Stop() { | |
433 if (!g_main_loop_is_running(main_loop_)) | |
434 fprintf(stderr, "%s:%d: main loop not running!\n", __FILE__, __LINE__); | |
435 g_main_loop_quit(main_loop_); | |
436 } | |
437 | |
438 void KeyringProxy::ReadData() { | |
vandebo (ex-Chrome)
2011/12/01 19:57:30
This method looks very similar to another on the o
| |
439 // We don't need to read all the available data. If there is still data left | |
440 // after we return, then we'll get called again because the descriptor will | |
441 // still be ready to read. So just read a fixed amount, for simplicity. | |
442 char buffer[256]; | |
443 ssize_t available = read(fd_, buffer, sizeof(buffer)); | |
444 if (available > 0) { | |
445 // Got some data. Handle it. Note that this may not be a complete message. | |
446 ssize_t used = 0; | |
447 while (used < available) { | |
448 ssize_t handled = reader_.HandleData(&buffer[used], available); | |
449 assert(handled > 0); | |
450 used += handled; | |
451 available -= handled; | |
452 if (reader_.is_complete()) { | |
453 if (!processor_->Process(reader_.lines())) { | |
454 // A fatal processing error occurred. Shut down the proxy. | |
455 Stop(); | |
456 } | |
457 reader_.Reset(); | |
458 } else { | |
459 assert(available == 0); | |
460 } | |
461 } | |
462 } else if (available == 0 || errno != EINTR) { | |
463 // EOF, or an unexpected error. Shut down the proxy. | |
464 Stop(); | |
465 } | |
466 } | |
467 | |
468 // static | |
469 gboolean KeyringProxy::DataReady(gpointer data) { | |
470 static_cast<KeyringProxy*>(data)->ReadData(); | |
471 // We always keep this source. If we run out of input, we should quit. | |
472 return TRUE; | |
473 } | |
474 | |
475 } // namespace keyring_proxy | |
OLD | NEW |