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

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

Powered by Google App Engine
This is Rietveld 408576698