summaryrefslogtreecommitdiffstats
path: root/remoting/host/installer/mac/uninstaller/remoting_uninstaller.mm
blob: 7734d23d75edd215d06e4d1649e06fe771a7e123 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
// 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 != nullptr) {
    [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 = nullptr;
  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 != nullptr)
    fclose(pipe);
}

- (void)sudoDelete:(const char*)filename
         usingAuth:(AuthorizationRef)authRef  {
  const char* args[] = { "-rf", filename, nullptr };
  [self sudoCommand:"/bin/rm" withArguments:args usingAuth:authRef];
}

- (void)shutdownService {
  const char* launchCtl = "/bin/launchctl";
  const char* argsStop[] = { "stop", remoting::kServiceName, nullptr };
  [self runCommand:launchCtl withArguments:argsStop];

  if ([[NSFileManager defaultManager] fileExistsAtPath:
       [NSString stringWithUTF8String:remoting::kServicePlistPath]]) {
    const char* argsUnload[] = { "unload", "-w", "-S", "Aqua",
                                remoting::kServicePlistPath, nullptr };
    [self runCommand:launchCtl withArguments:argsUnload];
  }
}

- (void)keystoneUnregisterUsingAuth:(AuthorizationRef)authRef {
  const char* args[] = {"--delete", "--productid", kKeystonePID, "-S", nullptr};
  [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(nullptr, kAuthorizationEmptyEnvironment,
                          kAuthorizationFlagDefaults, authRef.get_pointer());
  if (status != errAuthorizationSuccess) {
    [NSException raise:@"AuthorizationCreate Failure"
                format:@"Error during AuthorizationCreate status=%d",
                           static_cast<int>(status)];
  }

  AuthorizationItem right = {kAuthorizationRightExecute, 0, nullptr, 0};
  AuthorizationRights rights = {1, &right};
  AuthorizationFlags flags = kAuthorizationFlagDefaults |
                             kAuthorizationFlagInteractionAllowed |
                             kAuthorizationFlagPreAuthorize |
                             kAuthorizationFlagExtendRights;
  status = AuthorizationCopyRights(authRef, &rights, nullptr, flags, nullptr);
  if (status == errAuthorizationSuccess) {
    RemotingUninstaller* uninstaller =
        [[[RemotingUninstaller alloc] init] autorelease];
    [uninstaller remotingUninstallUsingAuth:authRef];
  }
  return status;
}

@end