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

Side by Side Diff: remoting/host/me2me_preference_pane.mm

Issue 10171020: Preference Pane for chromoting on Mac (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Save xib in IBuilder 3.2.6 Created 8 years, 7 months 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
« no previous file with comments | « remoting/host/me2me_preference_pane.h ('k') | remoting/host/me2me_preference_pane.xib » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 #import "remoting/host/me2me_preference_pane.h"
6
7 #import <Cocoa/Cocoa.h>
8 #include <launch.h>
9 #import <PreferencePanes/PreferencePanes.h>
10 #import <SecurityInterface/SFAuthorizationView.h>
11
12 #include "base/eintr_wrapper.h"
13 #include "base/file_path.h"
14 #include "base/file_util.h"
15 #include "base/logging.h"
16 #include "base/mac/authorization_util.h"
17 #include "base/mac/foundation_util.h"
18 #include "base/mac/launchd.h"
19 #include "base/mac/mac_logging.h"
20 #include "base/mac/scoped_launch_data.h"
21 #include "base/memory/scoped_ptr.h"
22 #include "base/stringprintf.h"
23 #include "base/sys_string_conversions.h"
24 #include "remoting/host/host_config.h"
25 #include "remoting/host/json_host_config.h"
26 #include "remoting/protocol/me2me_host_authenticator_factory.h"
27
28 namespace {
29 // The name of the Remoting Host service that is registered with launchd.
30 #define kServiceName "org.chromium.chromoting"
31 #define kConfigDir "/Library/PrivilegedHelperTools/"
32
33 // This helper script is executed as root. It is passed a command-line option
34 // (--enable or --disable), which causes it to create or remove a file that
35 // informs the host's launch script of whether the host is enabled or disabled.
36 const char kHelperTool[] = kConfigDir kServiceName ".me2me.sh";
37
38 bool GetTemporaryConfigFilePath(FilePath* path) {
39 if (!file_util::GetTempDir(path))
40 return false;
41 *path = path->Append(kServiceName ".json");
42 return true;
43 }
44
45 bool IsConfigValid(const remoting::JsonHostConfig* config) {
46 std::string value;
47 return (config->GetString(remoting::kHostIdConfigPath, &value) &&
48 config->GetString(remoting::kHostSecretHashConfigPath, &value) &&
49 config->GetString(remoting::kXmppLoginConfigPath, &value));
50 }
51
52 bool IsPinValid(const std::string& pin, const std::string& host_id,
53 const std::string& host_secret_hash) {
54 remoting::protocol::SharedSecretHash hash;
55 if (!hash.Parse(host_secret_hash)) {
56 LOG(ERROR) << "Invalid host_secret_hash.";
57 return false;
58 }
59 std::string result =
60 remoting::protocol::AuthenticationMethod::ApplyHashFunction(
61 hash.hash_function, host_id, pin);
62 return result == hash.value;
63 }
64
65 } // namespace
66
67
68 @implementation Me2MePreferencePane
69
70 - (void)mainViewDidLoad {
71 [authorization_view_ setDelegate:self];
72 [authorization_view_ setString:kAuthorizationRightExecute];
73 [authorization_view_ setAutoupdate:YES];
74 }
75
76 - (void)willSelect {
77 have_new_config_ = NO;
78
79 NSDistributedNotificationCenter* center =
80 [NSDistributedNotificationCenter defaultCenter];
81 [center addObserver:self
82 selector:@selector(onNewConfigFile:)
83 name:@kServiceName
84 object:nil];
85
86 service_status_timer_ =
87 [[NSTimer scheduledTimerWithTimeInterval:2.0
88 target:self
89 selector:@selector(refreshServiceStatus:)
90 userInfo:nil
91 repeats:YES] retain];
92 [self updateServiceStatus];
93 [self updateAuthorizationStatus];
94 [self readNewConfig];
95 [self updateUI];
96 }
97
98 - (void)willUnselect {
99 NSDistributedNotificationCenter* center =
100 [NSDistributedNotificationCenter defaultCenter];
101 [center removeObserver:self];
102
103 [service_status_timer_ invalidate];
104 [service_status_timer_ release];
105 service_status_timer_ = nil;
106 }
107
108 - (void)onApply:(id)sender {
109 if (!have_new_config_) {
110 // It shouldn't be possible to hit the button if there is no config to
111 // apply, but check anyway just in case it happens somehow.
112 return;
113 }
114
115 // Ensure the authorization token is up-to-date before using it.
116 [self updateAuthorizationStatus];
117 [self updateUI];
118
119 std::string pin = base::SysNSStringToUTF8([pin_ stringValue]);
120 std::string host_id, host_secret_hash;
121 bool result = (config_->GetString(remoting::kHostIdConfigPath, &host_id) &&
122 config_->GetString(remoting::kHostSecretHashConfigPath,
123 &host_secret_hash));
124 DCHECK(result);
125 if (!IsPinValid(pin, host_id, host_secret_hash)) {
126 [self showIncorrectPinMessage];
127 return;
128 }
129
130 [self applyNewServiceConfig];
131 [self updateUI];
132 }
133
134 - (void)onDisable:(id)sender {
135 // Ensure the authorization token is up-to-date before using it.
136 [self updateAuthorizationStatus];
137 [self updateUI];
138 if (!is_pane_unlocked_)
139 return;
140
141 if (![self runHelperAsRootWithCommand:"--disable"
142 inputData:""]) {
143 LOG(ERROR) << "Failed to run the helper tool";
144 [self showError];
145 return;
146 }
147
148 // Stop the launchd job. This cannot easily be done by the helper tool,
149 // since the launchd job runs in the current user's context.
150 [self sendJobControlMessage:LAUNCH_KEY_STOPJOB];
151 }
152
153 - (void)onNewConfigFile:(NSNotification*)notification {
154 [self readNewConfig];
155 [self updateUI];
156 }
157
158 - (void)refreshServiceStatus:(NSTimer*)timer {
159 BOOL was_running = is_service_running_;
160 [self updateServiceStatus];
161 if (was_running != is_service_running_)
162 [self updateUI];
163 }
164
165 - (void)authorizationViewDidAuthorize:(SFAuthorizationView*)view {
166 [self updateAuthorizationStatus];
167 [self updateUI];
168 }
169
170 - (void)authorizationViewDidDeauthorize:(SFAuthorizationView*)view {
171 [self updateAuthorizationStatus];
172 [self updateUI];
173 }
174
175 - (void)updateServiceStatus {
176 pid_t job_pid = base::mac::PIDForJob(kServiceName);
177 is_service_running_ = (job_pid > 0);
178 }
179
180 - (void)updateAuthorizationStatus {
181 is_pane_unlocked_ = [authorization_view_ updateStatus:authorization_view_];
182 }
183
184 - (void)readNewConfig {
185 FilePath file;
186 if (!GetTemporaryConfigFilePath(&file)) {
187 LOG(ERROR) << "Failed to get path of configuration data.";
188 [self showError];
189 return;
190 }
191 if (!file_util::PathExists(file))
192 return;
193
194 scoped_ptr<remoting::JsonHostConfig> new_config_(
195 new remoting::JsonHostConfig(file));
196 if (!new_config_->Read()) {
197 // Report the error, because the file exists but couldn't be read. The
198 // case of non-existence is normal and expected.
199 LOG(ERROR) << "Error reading configuration data from " << file.value();
200 [self showError];
201 return;
202 }
203 file_util::Delete(file, false);
204 if (!IsConfigValid(new_config_.get())) {
205 LOG(ERROR) << "Invalid configuration data read.";
206 [self showError];
207 return;
208 }
209
210 config_.swap(new_config_);
211 have_new_config_ = YES;
212 }
213
214 - (void)updateUI {
215 // TODO(lambroslambrou): These strings should be localized.
216 #ifdef OFFICIAL_BUILD
217 NSString* name = @"Chrome Remote Desktop";
218 #else
219 NSString* name = @"Chromoting";
220 #endif
221 NSString* message;
222 if (is_service_running_) {
223 message = [NSString stringWithFormat:@"%@ is enabled", name];
224 } else {
225 message = [NSString stringWithFormat:@"%@ is disabled", name];
226 }
227 [status_message_ setStringValue:message];
228
229 std::string email;
230 if (config_.get()) {
231 bool result = config_->GetString(remoting::kXmppLoginConfigPath, &email);
232
233 // The config has already been checked by |IsConfigValid|.
234 DCHECK(result);
235 }
236 [email_ setStringValue:base::SysUTF8ToNSString(email)];
237
238 [disable_button_ setEnabled:(is_pane_unlocked_ && is_service_running_)];
239 [pin_instruction_message_ setEnabled:have_new_config_];
240 [email_ setEnabled:have_new_config_];
241 [pin_ setEnabled:have_new_config_];
242 [apply_button_ setEnabled:(is_pane_unlocked_ && have_new_config_)];
243 }
244
245 - (void)showError {
246 NSAlert* alert = [[NSAlert alloc] init];
247 [alert setMessageText:@"An unexpected error occurred."];
248 [alert setInformativeText:@"Check the system log for more information."];
249 [alert setAlertStyle:NSWarningAlertStyle];
250 [alert beginSheetModalForWindow:[[self mainView] window]
251 modalDelegate:nil
252 didEndSelector:nil
253 contextInfo:nil];
254 [alert release];
255 }
256
257 - (void)showIncorrectPinMessage {
258 NSAlert* alert = [[NSAlert alloc] init];
259 [alert setMessageText:@"Incorrect PIN entered."];
260 [alert setAlertStyle:NSWarningAlertStyle];
261 [alert beginSheetModalForWindow:[[self mainView] window]
262 modalDelegate:nil
263 didEndSelector:nil
264 contextInfo:nil];
265 [alert release];
266 }
267
268 - (void)applyNewServiceConfig {
269 [self updateServiceStatus];
270 std::string serialized_config = config_->GetSerializedData();
271 const char* command = is_service_running_ ? "--save-config" : "--enable";
272 if (![self runHelperAsRootWithCommand:command
273 inputData:serialized_config]) {
274 LOG(ERROR) << "Failed to run the helper tool";
275 [self showError];
276 return;
277 }
278
279 have_new_config_ = NO;
280
281 // If the service is running, send a signal to cause it to reload its
282 // configuration, otherwise start the service.
283 if (is_service_running_) {
284 pid_t job_pid = base::mac::PIDForJob(kServiceName);
285 if (job_pid > 0) {
286 kill(job_pid, SIGHUP);
287 } else {
288 LOG(ERROR) << "Failed to obtain PID of service " << kServiceName;
289 [self showError];
290 }
291 } else {
292 [self sendJobControlMessage:LAUNCH_KEY_STARTJOB];
293 }
294 }
295
296 - (BOOL)runHelperAsRootWithCommand:(const char*)command
297 inputData:(const std::string&)input_data {
298 AuthorizationRef authorization =
299 [[authorization_view_ authorization] authorizationRef];
300 if (!authorization) {
301 LOG(ERROR) << "Failed to obtain authorizationRef";
302 return NO;
303 }
304
305 // TODO(lambroslambrou): Replace the deprecated ExecuteWithPrivileges
306 // call with a launchd-based helper tool, which is more secure.
307 // http://crbug.com/120903
308 const char* arguments[] = { command, NULL };
309 FILE* pipe = NULL;
310 pid_t pid;
311 OSStatus status = base::mac::ExecuteWithPrivilegesAndGetPID(
312 authorization,
313 kHelperTool,
314 kAuthorizationFlagDefaults,
315 arguments,
316 &pipe,
317 &pid);
318 if (status != errAuthorizationSuccess) {
319 OSSTATUS_LOG(ERROR, status) << "AuthorizationExecuteWithPrivileges";
320 return NO;
321 }
322 if (pid == -1) {
323 LOG(ERROR) << "Failed to get child PID";
324 if (pipe)
325 fclose(pipe);
326
327 return NO;
328 }
329 if (!pipe) {
330 LOG(ERROR) << "Unexpected NULL pipe";
331 return NO;
332 }
333
334 // Some cleanup is needed (closing the pipe and waiting for the child
335 // process), so flag any errors before returning.
336 BOOL error = NO;
337
338 if (!input_data.empty()) {
339 size_t bytes_written = fwrite(input_data.data(), sizeof(char),
340 input_data.size(), pipe);
341 // According to the fwrite manpage, a partial count is returned only if a
342 // write error has occurred.
343 if (bytes_written != input_data.size()) {
344 LOG(ERROR) << "Failed to write data to child process";
345 error = YES;
346 }
347 }
348
349 // In all cases, fclose() should be called with the returned FILE*. In the
350 // case of sending data to the child, this needs to be done before calling
351 // waitpid(), since the child reads until EOF on its stdin, so calling
352 // waitpid() first would result in deadlock.
353 if (fclose(pipe) != 0) {
354 PLOG(ERROR) << "fclose";
355 error = YES;
356 }
357
358 int exit_status;
359 pid_t wait_result = HANDLE_EINTR(waitpid(pid, &exit_status, 0));
360 if (wait_result != pid) {
361 PLOG(ERROR) << "waitpid";
362 error = YES;
363 }
364
365 // No more cleanup needed.
366 if (error)
367 return NO;
368
369 if (WIFEXITED(exit_status) && WEXITSTATUS(exit_status) == 0) {
370 return YES;
371 } else {
372 LOG(ERROR) << kHelperTool << " failed with exit status " << exit_status;
373 return NO;
374 }
375 }
376
377 - (BOOL)sendJobControlMessage:(const char*)launch_key {
378 base::mac::ScopedLaunchData response(
379 base::mac::MessageForJob(kServiceName, launch_key));
380 if (!response) {
381 LOG(ERROR) << "Failed to send message to launchd";
382 [self showError];
383 return NO;
384 }
385
386 // Expect a response of type LAUNCH_DATA_ERRNO.
387 launch_data_type_t type = launch_data_get_type(response.get());
388 if (type != LAUNCH_DATA_ERRNO) {
389 LOG(ERROR) << "launchd returned unexpected type: " << type;
390 [self showError];
391 return NO;
392 }
393
394 int error = launch_data_get_errno(response.get());
395 if (error) {
396 LOG(ERROR) << "launchd returned error: " << error;
397 [self showError];
398 return NO;
399 }
400 return YES;
401 }
402
403 @end
OLDNEW
« no previous file with comments | « remoting/host/me2me_preference_pane.h ('k') | remoting/host/me2me_preference_pane.xib » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698