|
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_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 | |
OLD | NEW |