Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(149)

Side by Side Diff: chrome/browser/password_manager/keyring_proxy/keyring_proxy_client.cc

Issue 8509038: Linux: split GNOME Keyring integration into a separate process. Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: fix test ordering dependency Created 9 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(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_client.h"
6
7 #include <errno.h>
8 #include <sys/socket.h>
9 #include <unistd.h>
10
11 #include "base/base_paths.h"
12 #include "base/bind.h"
13 #include "base/file_path.h"
14 #include "base/message_loop.h"
15 #include "base/message_pump_libevent.h"
16 #include "base/path_service.h"
17 #include "base/process_util.h"
18 #include "base/string_number_conversions.h"
19 #include "base/stringprintf.h"
20 #include "base/utf_string_conversions.h"
21 #include "chrome/browser/password_manager/keyring_proxy/keyring_proxy.h"
22 #include "chrome/browser/password_manager/keyring_proxy/message_reader.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "webkit/glue/password_form.h"
25
26 using content::BrowserThread;
27
28 namespace keyring_proxy {
29
30 // We need a MessageLoopForIO to watch a file descriptor, but the DB thread
31 // doesn't have one. This class handles actually watching and reading from the
32 // file descriptor on the file thread, then notifying the keyring proxy which
33 // parses the messages and notifies the native backend on the DB thread.
34 class KeyringProxyFDWatcher
35 : public base::RefCounted<KeyringProxyFDWatcher>,
36 public base::MessagePumpLibevent::Watcher {
37 public:
38 // Takes ownership of proxy_fd and will close it in the destructor.
39 // Does not take ownership of |client|. ShutDown() must be called before
40 // the client is destroyed to prevent further callbacks; the client
41 // should keep a reference to the new KeyringProxyFDWatcher until then.
42 KeyringProxyFDWatcher(int proxy_fd, KeyringProxyClient* client);
43
44 void Init();
45
46 int fd() const { return proxy_fd_; }
47
48 void ShutDown();
49
50 private:
51 friend class base::RefCounted<KeyringProxyFDWatcher>;
52 ~KeyringProxyFDWatcher();
53
54 // These run on the file thread.
55 void StartWatching();
56 void StopWatching();
57
58 // Implement base::MessagePumpLibevent::Watcher.
59 virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
60 virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE;
61
62 const int proxy_fd_;
63 KeyringProxyClient* client_;
64 base::MessagePumpLibevent::FileDescriptorWatcher ipc_watcher_;
65
66 // Used to batch data read from the proxy into individual replies.
67 MessageReader reader_;
68
69 DISALLOW_COPY_AND_ASSIGN(KeyringProxyFDWatcher);
70 };
71
72 KeyringProxyFDWatcher::KeyringProxyFDWatcher(int proxy_fd,
73 KeyringProxyClient* client)
74 : proxy_fd_(proxy_fd), client_(client) {
75 // It's not safe to post a task referencing a reference counted object from
76 // within its constructor: the execution of the task races with the
77 // constructor returning and the reference it had may be removed before
78 // another is created. So, we have an Init() method below instead.
79 }
80
81 void KeyringProxyFDWatcher::Init() {
82 if (client_) {
83 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
84 base::Bind(&KeyringProxyFDWatcher::StartWatching,
85 this));
86 }
87 }
88
89 void KeyringProxyFDWatcher::ShutDown() {
90 // This will prevent any calls back to the client. It is technically
vandebo (ex-Chrome) 2011/12/01 19:57:30 Shutdown crashes can be a pain in the butt to diag
91 // insufficient as this runs on the UI thread and the check occurs on the file
92 // thread, so the file thread may have already checked it before we clear it.
93 // We could fix that with a lock held while client callbacks run on the file
94 // thread, but for now just ignore it - it would only be a problem if you were
95 // actively using the proxy during shutdown, which probably shouldn't happen.
96 client_ = NULL;
97 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
98 base::Bind(&KeyringProxyFDWatcher::StopWatching,
99 this));
100 }
101
102 KeyringProxyFDWatcher::~KeyringProxyFDWatcher() {
103 // It should not be necessary to stop watching here, since we should already
104 // have done that in StopWatching() prior to the last reference going away.
105 // We do it anyway just to be extra sure.
106 ipc_watcher_.StopWatchingFileDescriptor();
vandebo (ex-Chrome) 2011/12/01 19:57:30 Will this always be called on the right thread? Ad
107 close(proxy_fd_);
108 }
109
110 void KeyringProxyFDWatcher::StartWatching() {
111 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
112 MessageLoopForIO* file_loop = MessageLoopForIO::current();
113 bool ok = file_loop->WatchFileDescriptor(
114 proxy_fd_, true, MessageLoopForIO::WATCH_READ, &ipc_watcher_, this);
115 DCHECK(ok);
116 }
117
118 void KeyringProxyFDWatcher::StopWatching() {
119 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
120 ipc_watcher_.StopWatchingFileDescriptor();
121 }
122
123 void KeyringProxyFDWatcher::OnFileCanReadWithoutBlocking(int fd) {
124 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
125 DCHECK_EQ(proxy_fd_, fd);
126 // We don't need to read all the available data. If there is still data left
127 // after we return, then we'll get called again because the descriptor will
128 // still be ready to read. So just read a fixed amount, for simplicity.
129 char buffer[256];
vandebo (ex-Chrome) 2011/12/01 19:57:30 constant somewhere?
130 ssize_t available = read(proxy_fd_, buffer, sizeof(buffer));
131 if (available > 0) {
132 // Got some data. Handle it. Note that this may not be a complete message.
133 ssize_t used = 0;
134 while (used < available) {
135 ssize_t handled = reader_.HandleData(&buffer[used], available);
136 DCHECK_GT(handled, 0);
137 used += handled;
138 available -= handled;
139 if (reader_.is_complete()) {
140 if (client_)
141 client_->HandleProxyReply(reader_.lines());
142 reader_.Reset();
143 } else {
144 DCHECK_EQ(0, available);
145 }
146 }
147 } else if (available == 0 || errno != EINTR) {
148 // EOF, or an unexpected error. Stop watching and notify.
149 LOG(WARNING) << "Unexpected failure reading from keyring proxy: " << errno;
150 ipc_watcher_.StopWatchingFileDescriptor();
151 if (client_)
152 client_->HandleProxyError(available == 0);
153 }
154 // Note that if the read is interrupted, we'll just end up here. That's fine.
155 // The file descriptor will still be ready and we'll get called again soon.
156 }
157
158 void KeyringProxyFDWatcher::OnFileCanWriteWithoutBlocking(int fd) {
159 // We didn't ask to be notified of writability.
160 NOTREACHED();
161 }
162
163 const char KeyringProxyClient::kProxyBinary[] = "chrome-keyring-proxy";
164
165 KeyringProxyClient::KeyringProxyClient() : next_request_id_(0) {
166 }
167
168 KeyringProxyClient::~KeyringProxyClient() {
169 base::AutoLock lock(request_lock_);
170 if (fd_watcher_.get())
171 fd_watcher_->ShutDown();
172 CancelAllRequests();
173 }
174
175 bool KeyringProxyClient::Connect() {
176 base::AutoLock lock(request_lock_);
177 DCHECK(!fd_watcher_.get());
178 int fd = LaunchKeyringProxy();
179 if (fd < 0)
180 return false;
181 fd_watcher_ = new KeyringProxyFDWatcher(fd, this);
182 fd_watcher_->Init();
183 return true;
184 }
185
186 void KeyringProxyClient::ConnectForTesting(int fd, bool watch_for_reads) {
187 if (watch_for_reads) {
188 fd_watcher_ = new KeyringProxyFDWatcher(fd, this);
189 fd_watcher_->Init();
190 } else {
191 fd_watcher_ = new KeyringProxyFDWatcher(fd, NULL);
192 // There's no need to call Init(), as we passed NULL for |client| above.
193 }
194 }
195
196 void KeyringProxyClient::AddLogin(const PasswordForm& form,
197 const std::string& app_string,
198 RequestContext* context) {
199 // If we are asked to save a password with 0 date, use the current time.
200 // We don't want to actually save passwords as though on January 1, 1970.
201 time_t date_created = form.date_created.ToTimeT();
202 if (!date_created)
203 date_created = time(NULL);
204 ProxyMessage request;
205 request.push_back("+" + form.origin.spec()); // Display name.
206 request.push_back("+" + UTF16ToUTF8(form.password_value));
207 request.push_back("+" + form.origin.spec()); // Origin.
208 request.push_back("+" + form.action.spec());
209 request.push_back("+" + UTF16ToUTF8(form.username_element));
210 request.push_back("+" + UTF16ToUTF8(form.username_value));
211 request.push_back("+" + UTF16ToUTF8(form.password_element));
212 request.push_back("+" + UTF16ToUTF8(form.submit_element));
213 request.push_back("+" + form.signon_realm);
214 request.push_back(form.ssl_valid ? "1" : "0");
215 request.push_back(form.preferred ? "1" : "0");
216 request.push_back("+" + base::Int64ToString(date_created));
217 request.push_back(form.blacklisted_by_user ? "1" : "0");
218 request.push_back(StringPrintf("%d", form.scheme));
219 request.push_back(app_string);
220 SendRequest(KeyringProxy::kAddLoginCommand, request, context);
221 }
222
223 void KeyringProxyClient::AddLoginSearch(const PasswordForm& form,
224 const std::string& app_string,
225 RequestContext* context) {
226 ProxyMessage request;
227 request.push_back("+" + form.origin.spec());
228 request.push_back("+" + UTF16ToUTF8(form.username_element));
229 request.push_back("+" + UTF16ToUTF8(form.username_value));
230 request.push_back("+" + UTF16ToUTF8(form.password_element));
231 request.push_back("+" + UTF16ToUTF8(form.submit_element));
232 request.push_back("+" + form.signon_realm);
233 request.push_back(app_string);
234 SendRequest(KeyringProxy::kAddLoginSearchCommand, request, context);
235 }
236
237 void KeyringProxyClient::UpdateLoginSearch(const PasswordForm& form,
238 const std::string& app_string,
239 RequestContext* context) {
240 ProxyMessage request;
241 request.push_back("+" + form.origin.spec());
242 request.push_back("+" + UTF16ToUTF8(form.username_element));
243 request.push_back("+" + UTF16ToUTF8(form.username_value));
244 request.push_back("+" + UTF16ToUTF8(form.password_element));
245 request.push_back("+" + form.signon_realm);
246 request.push_back(app_string);
247 SendRequest(KeyringProxy::kUpdateLoginSearchCommand, request, context);
248 }
249
250 void KeyringProxyClient::RemoveLogin(const PasswordForm& form,
251 const std::string& app_string,
252 RequestContext* context) {
253 ProxyMessage request;
254 request.push_back("+" + form.origin.spec());
255 request.push_back("+" + form.action.spec());
256 request.push_back("+" + UTF16ToUTF8(form.username_element));
257 request.push_back("+" + UTF16ToUTF8(form.username_value));
258 request.push_back("+" + UTF16ToUTF8(form.password_element));
259 request.push_back("+" + UTF16ToUTF8(form.submit_element));
260 request.push_back("+" + form.signon_realm);
261 request.push_back(app_string);
262 SendRequest(KeyringProxy::kRemoveLoginCommand, request, context);
263 }
264
265 void KeyringProxyClient::GetLogins(const PasswordForm& form,
266 const std::string& app_string,
267 RequestContext* context) {
268 ProxyMessage request;
269 request.push_back("+" + form.signon_realm);
270 request.push_back(app_string);
271 SendRequest(KeyringProxy::kGetLoginsCommand, request, context);
272 }
273
274 void KeyringProxyClient::GetLoginsList(bool blacklisted_by_user,
275 const std::string& app_string,
276 RequestContext* context) {
277 ProxyMessage request;
278 request.push_back(blacklisted_by_user ? "1" : "0");
279 request.push_back(app_string);
280 SendRequest(KeyringProxy::kGetLoginsListCommand, request, context);
281 }
282
283 void KeyringProxyClient::GetAllLogins(const std::string& app_string,
284 RequestContext* context) {
285 ProxyMessage request;
286 request.push_back(app_string);
287 SendRequest(KeyringProxy::kGetAllLoginsCommand, request, context);
288 }
289
290 // static
291 int KeyringProxyClient::LaunchKeyringProxy() {
292 FilePath chrome_dir;
293 if (!PathService::Get(base::DIR_EXE, &chrome_dir))
294 return -1;
295 std::vector<std::string> proxy_command;
296 proxy_command.push_back(chrome_dir.Append(kProxyBinary).value());
297
298 int fds[2];
299 if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds) < 0)
300 return -1;
301 base::file_handle_mapping_vector ipc;
302 ipc.push_back(std::make_pair(fds[1], STDIN_FILENO));
303 ipc.push_back(std::make_pair(fds[1], STDOUT_FILENO));
304 base::LaunchOptions options;
305 options.fds_to_remap = &ipc;
306
307 base::ProcessHandle proxy_handle;
308 if (!base::LaunchProcess(proxy_command, options, &proxy_handle)) {
309 close(fds[0]);
310 close(fds[1]);
311 return -1;
312 }
313 close(fds[1]);
314 return fds[0];
315 }
316
317 int KeyringProxyClient::GetRequestId() {
318 for (;;) {
vandebo (ex-Chrome) 2011/12/01 19:57:30 Any reason not to use while(true) { ?
319 // |next_request_id_| is actually the most recently used request ID, because
vandebo (ex-Chrome) 2011/12/01 19:57:30 Can we then name it appropriately? last_request_id
320 // that makes it easier to write this loop with a preincrement operator.
321 if (++next_request_id_ < 0)
322 next_request_id_ = 1;
323 if (proxy_requests_.find(next_request_id_) == proxy_requests_.end())
324 return next_request_id_;
325 }
326 }
327
328 void KeyringProxyClient::CancelRequest(RequestContext* context) {
329 context->result_code = GNOME_KEYRING_RESULT_CANCELLED;
330 context->event.Signal();
331 }
332
333 void KeyringProxyClient::CancelAllRequests() {
334 for (std::map<int, RequestContext*>::iterator it = proxy_requests_.begin();
vandebo (ex-Chrome) 2011/12/01 19:57:30 request_lock_.AssertAcquired();
335 it != proxy_requests_.end(); ++it) {
336 CancelRequest(it->second);
337 }
338 proxy_requests_.clear();
339 }
340
341 void KeyringProxyClient::SendRequest(char type,
342 const ProxyMessage& request,
343 RequestContext* context) {
344 base::AutoLock lock(request_lock_);
345 if (!fd_watcher_.get()) {
346 LOG(WARNING) << "Attempted to use unconnected keyring proxy";
347 CancelRequest(context);
348 return;
349 }
350 int id = GetRequestId();
351 proxy_requests_[id] = context;
352 std::string message = StringPrintf("%c%d\n", type, id);
353 for (size_t i = 0; i < request.size(); ++i)
vandebo (ex-Chrome) 2011/12/01 19:57:30 You put the message in a vector of strings and the
354 message += request[i] + "\n";
355 message += "\n";
356 size_t written = write(fd_watcher_->fd(), message.data(), message.size());
vandebo (ex-Chrome) 2011/12/01 19:57:30 Can you compose your message first, then get the l
357 if (written != message.size()) {
358 LOG(ERROR) << "Failed to write to keyring proxy! Likely failure soon...";
359 CancelRequest(context);
360 }
361 }
362
363 void KeyringProxyClient::HandleProxyReply(const ProxyMessage& reply) {
364 if (!reply.size()) {
365 LOG(WARNING) << "Got empty reply from keyring proxy";
366 return;
367 }
368 int id, result_code;
369 if (sscanf(reply[0].c_str(), "%d %d", &id, &result_code) != 2) {
370 // We haven't acquired the lock yet so we can just use HandleProxyError().
371 HandleProxyError(false);
372 return;
373 }
374 base::AutoLock lock(request_lock_);
375 std::map<int, RequestContext*>::iterator it = proxy_requests_.find(id);
376 if (it == proxy_requests_.end()) {
377 LOG(WARNING) << "Unexpected reply from keyring proxy (ID " << id << ")";
378 return;
379 }
380 RequestContext* context = it->second;
381 const size_t kLinesPerPass = 13;
vandebo (ex-Chrome) 2011/12/01 19:57:30 This isn't so much lines per pass, but lines per r
382 if ((reply.size() - 1) % kLinesPerPass != 0 ||
383 (reply.size() > 1 && !context->result_list)) {
384 LOG(WARNING) << "Invalid reply from keyring proxy (ID " << id << ")";
385 fd_watcher_->ShutDown();
386 fd_watcher_ = NULL;
387 CancelAllRequests();
388 return;
389 }
390 // We don't remove the request from the map until after the checks above.
391 proxy_requests_.erase(it);
392 context->result_code = static_cast<GnomeKeyringResult>(result_code);
393 for (size_t i = 1; i + kLinesPerPass <= reply.size(); i += kLinesPerPass) {
394 PasswordForm* form = new PasswordForm;
395 form->origin = GURL(reply[i].substr(1));
396 form->action = GURL(reply[i + 1].substr(1));
397 form->username_element = UTF8ToUTF16(reply[i + 2].substr(1));
398 form->username_value = UTF8ToUTF16(reply[i + 3].substr(1));
399 form->password_element = UTF8ToUTF16(reply[i + 4].substr(1));
400 form->password_value = UTF8ToUTF16(reply[i + 5].substr(1));
401 form->submit_element = UTF8ToUTF16(reply[i + 6].substr(1));
402 form->signon_realm = reply[i + 7].substr(1);
403 form->ssl_valid = reply[i + 8] != "0";
404 form->preferred = reply[i + 9] != "0";
405 int64 date_created = 0;
406 bool date_ok = base::StringToInt64(reply[i + 10].substr(1), &date_created);
407 DCHECK(date_ok);
408 form->date_created = base::Time::FromTimeT(date_created);
409 form->blacklisted_by_user = reply[i + 11] != "0";
410 unsigned int scheme = 0;
411 int scheme_ok = sscanf(reply[i + 12].c_str(), "%u", &scheme);
412 DCHECK_EQ(1, scheme_ok);
413 form->scheme = static_cast<PasswordForm::Scheme>(scheme);
414 context->result_list->push_back(form);
415 }
416 context->event.Signal();
417 }
418
419 void KeyringProxyClient::HandleProxyError(bool eof) {
420 base::AutoLock lock(request_lock_);
421 if (eof)
422 LOG(WARNING) << "Unexpected EOF reading from keyring proxy";
423 else
424 LOG(WARNING) << "Invalid reply from keyring proxy";
425 fd_watcher_->ShutDown();
426 fd_watcher_ = NULL;
427 CancelAllRequests();
428 }
429
430 } // namespace keyring_proxy
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698