// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "remoting/host/installer/mac/uninstaller/remoting_uninstaller.h" #import <Cocoa/Cocoa.h> #include "base/mac/scoped_authorizationref.h" #include "remoting/host/constants_mac.h" void logOutput(FILE* pipe) { char readBuffer[128]; for (;;) { long bytesRead = read(fileno(pipe), readBuffer, sizeof(readBuffer) - 1); if (bytesRead < 1) break; readBuffer[bytesRead] = '\0'; NSLog(@"%s", readBuffer); } } NSArray* convertToNSArray(const char** array) { NSMutableArray* ns_array = [[[NSMutableArray alloc] init] autorelease]; int i = 0; const char* element = array[i++]; while (element != NULL) { [ns_array addObject:[NSString stringWithUTF8String:element]]; element = array[i++]; } return ns_array; } @implementation RemotingUninstaller // Keystone const char kKeystoneAdmin[] = "/Library/Google/GoogleSoftwareUpdate/" "GoogleSoftwareUpdate.bundle/Contents/MacOS/" "ksadmin"; const char kKeystonePID[] = "com.google.chrome_remote_desktop"; - (void)runCommand:(const char*)cmd withArguments:(const char**)args { NSTask* task; NSPipe* output = [NSPipe pipe]; NSString* result; NSArray* arg_array = convertToNSArray(args); NSLog(@"Executing: %s %@", cmd, [arg_array componentsJoinedByString:@" "]); @try { task = [[[NSTask alloc] init] autorelease]; [task setLaunchPath:[NSString stringWithUTF8String:cmd]]; [task setArguments:arg_array]; [task setStandardInput:[NSPipe pipe]]; [task setStandardOutput:output]; [task launch]; NSData* data = [[output fileHandleForReading] readDataToEndOfFile]; [task waitUntilExit]; if ([task terminationStatus] != 0) { // TODO(garykac): When we switch to sdk_10.6, show the // [task terminationReason] as well. NSLog(@"Command terminated status=%d", [task terminationStatus]); } result = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; if ([result length] != 0) { NSLog(@"Result: %@", result); } } @catch (NSException* exception) { NSLog(@"Exception %@ %@", [exception name], [exception reason]); } } - (void)sudoCommand:(const char*)cmd withArguments:(const char**)args usingAuth:(AuthorizationRef)authRef { NSArray* arg_array = convertToNSArray(args); NSLog(@"Executing (as Admin): %s %@", cmd, [arg_array componentsJoinedByString:@" "]); FILE* pipe = NULL; OSStatus status; status = AuthorizationExecuteWithPrivileges(authRef, cmd, kAuthorizationFlagDefaults, (char* const*)args, &pipe); if (status == errAuthorizationToolExecuteFailure) { NSLog(@"Error errAuthorizationToolExecuteFailure"); } else if (status != errAuthorizationSuccess) { NSLog(@"Error while executing %s. Status=%d", cmd, static_cast<int>(status)); } else { logOutput(pipe); } if (pipe != NULL) fclose(pipe); } - (void)sudoDelete:(const char*)filename usingAuth:(AuthorizationRef)authRef { const char* args[] = { "-rf", filename, NULL }; [self sudoCommand:"/bin/rm" withArguments:args usingAuth:authRef]; } - (void)shutdownService { const char* launchCtl = "/bin/launchctl"; const char* argsStop[] = { "stop", remoting::kServiceName, NULL }; [self runCommand:launchCtl withArguments:argsStop]; if ([[NSFileManager defaultManager] fileExistsAtPath: [NSString stringWithUTF8String:remoting::kServicePlistPath]]) { const char* argsUnload[] = { "unload", "-w", "-S", "Aqua", remoting::kServicePlistPath, NULL }; [self runCommand:launchCtl withArguments:argsUnload]; } } - (void)keystoneUnregisterUsingAuth:(AuthorizationRef)authRef { const char* args[] = { "--delete", "--productid", kKeystonePID, "-S", NULL }; [self sudoCommand:kKeystoneAdmin withArguments:args usingAuth:authRef]; } - (void)remotingUninstallUsingAuth:(AuthorizationRef)authRef { // Remove the enabled file before shutting down the service or else it might // restart itself. [self sudoDelete:remoting::kHostEnabledPath usingAuth:authRef]; [self shutdownService]; [self sudoDelete:remoting::kServicePlistPath usingAuth:authRef]; [self sudoDelete:remoting::kHostBinaryPath usingAuth:authRef]; [self sudoDelete:remoting::kHostHelperScriptPath usingAuth:authRef]; [self sudoDelete:remoting::kHostConfigFilePath usingAuth:authRef]; [self sudoDelete:remoting::kPrefPaneFilePath usingAuth:authRef]; [self sudoDelete:remoting::kLogFilePath usingAuth:authRef]; [self sudoDelete:remoting::kLogFileConfigPath usingAuth:authRef]; [self sudoDelete:remoting::kNativeMessagingManifestPath usingAuth:authRef]; [self sudoDelete:remoting::kBrandedUninstallerPath usingAuth:authRef]; [self sudoDelete:remoting::kUnbrandedUninstallerPath usingAuth:authRef]; [self keystoneUnregisterUsingAuth:authRef]; } - (OSStatus)remotingUninstall { base::mac::ScopedAuthorizationRef authRef; OSStatus status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authRef); if (status != errAuthorizationSuccess) { [NSException raise:@"AuthorizationCreate Failure" format:@"Error during AuthorizationCreate status=%d", static_cast<int>(status)]; } AuthorizationItem right = {kAuthorizationRightExecute, 0, NULL, 0}; AuthorizationRights rights = {1, &right}; AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights; status = AuthorizationCopyRights(authRef, &rights, NULL, flags, NULL); if (status == errAuthorizationSuccess) { RemotingUninstaller* uninstaller = [[[RemotingUninstaller alloc] init] autorelease]; [uninstaller remotingUninstallUsingAuth:authRef]; } return status; } @end