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#include "base/mac/authorization_util.h"
6
7#import <Foundation/Foundation.h>
8#include <sys/wait.h>
9
10#include <string>
11
12#include "base/basictypes.h"
13#include "base/logging.h"
14#include "base/mac/bundle_locations.h"
15#include "base/mac/mac_logging.h"
16#import "base/mac/mac_util.h"
17#include "base/mac/scoped_authorizationref.h"
18#include "base/posix/eintr_wrapper.h"
19#include "base/strings/string_number_conversions.h"
20#include "base/strings/string_util.h"
21
22namespace base {
23namespace mac {
24
25AuthorizationRef GetAuthorizationRightsWithPrompt(
26    AuthorizationRights* rights,
27    CFStringRef prompt,
28    AuthorizationFlags extraFlags) {
29  // Create an empty AuthorizationRef.
30  ScopedAuthorizationRef authorization;
31  OSStatus status = AuthorizationCreate(NULL,
32                                        kAuthorizationEmptyEnvironment,
33                                        kAuthorizationFlagDefaults,
34                                        &authorization);
35  if (status != errAuthorizationSuccess) {
36    OSSTATUS_LOG(ERROR, status) << "AuthorizationCreate";
37    return NULL;
38  }
39
40  AuthorizationFlags flags = kAuthorizationFlagDefaults |
41                             kAuthorizationFlagInteractionAllowed |
42                             kAuthorizationFlagExtendRights |
43                             kAuthorizationFlagPreAuthorize |
44                             extraFlags;
45
46  // product_logo_32.png is used instead of app.icns because Authorization
47  // Services can't deal with .icns files.
48  NSString* icon_path =
49      [base::mac::FrameworkBundle() pathForResource:@"product_logo_32"
50                                             ofType:@"png"];
51  const char* icon_path_c = [icon_path fileSystemRepresentation];
52  size_t icon_path_length = icon_path_c ? strlen(icon_path_c) : 0;
53
54  // The OS will append " Type an administrator's name and password to allow
55  // <CFBundleDisplayName> to make changes."
56  NSString* prompt_ns = base::mac::CFToNSCast(prompt);
57  const char* prompt_c = [prompt_ns UTF8String];
58  size_t prompt_length = prompt_c ? strlen(prompt_c) : 0;
59
60  AuthorizationItem environment_items[] = {
61    {kAuthorizationEnvironmentIcon, icon_path_length, (void*)icon_path_c, 0},
62    {kAuthorizationEnvironmentPrompt, prompt_length, (void*)prompt_c, 0}
63  };
64
65  AuthorizationEnvironment environment = {arraysize(environment_items),
66                                          environment_items};
67
68  status = AuthorizationCopyRights(authorization,
69                                   rights,
70                                   &environment,
71                                   flags,
72                                   NULL);
73
74  if (status != errAuthorizationSuccess) {
75    if (status != errAuthorizationCanceled) {
76      OSSTATUS_LOG(ERROR, status) << "AuthorizationCopyRights";
77    }
78    return NULL;
79  }
80
81  return authorization.release();
82}
83
84AuthorizationRef AuthorizationCreateToRunAsRoot(CFStringRef prompt) {
85  // Specify the "system.privilege.admin" right, which allows
86  // AuthorizationExecuteWithPrivileges to run commands as root.
87  AuthorizationItem right_items[] = {
88    {kAuthorizationRightExecute, 0, NULL, 0}
89  };
90  AuthorizationRights rights = {arraysize(right_items), right_items};
91
92  return GetAuthorizationRightsWithPrompt(&rights, prompt, 0);
93}
94
95OSStatus ExecuteWithPrivilegesAndGetPID(AuthorizationRef authorization,
96                                        const char* tool_path,
97                                        AuthorizationFlags options,
98                                        const char** arguments,
99                                        FILE** pipe,
100                                        pid_t* pid) {
101  // pipe may be NULL, but this function needs one.  In that case, use a local
102  // pipe.
103  FILE* local_pipe;
104  FILE** pipe_pointer;
105  if (pipe) {
106    pipe_pointer = pipe;
107  } else {
108    pipe_pointer = &local_pipe;
109  }
110
111  // AuthorizationExecuteWithPrivileges wants |char* const*| for |arguments|,
112  // but it doesn't actually modify the arguments, and that type is kind of
113  // silly and callers probably aren't dealing with that.  Put the cast here
114  // to make things a little easier on callers.
115  OSStatus status = AuthorizationExecuteWithPrivileges(authorization,
116                                                       tool_path,
117                                                       options,
118                                                       (char* const*)arguments,
119                                                       pipe_pointer);
120  if (status != errAuthorizationSuccess) {
121    return status;
122  }
123
124  int line_pid = -1;
125  size_t line_length = 0;
126  char* line_c = fgetln(*pipe_pointer, &line_length);
127  if (line_c) {
128    if (line_length > 0 && line_c[line_length - 1] == '\n') {
129      // line_c + line_length is the start of the next line if there is one.
130      // Back up one character.
131      --line_length;
132    }
133    std::string line(line_c, line_length);
134    if (!base::StringToInt(line, &line_pid)) {
135      // StringToInt may have set line_pid to something, but if the conversion
136      // was imperfect, use -1.
137      LOG(ERROR) << "ExecuteWithPrivilegesAndGetPid: funny line: " << line;
138      line_pid = -1;
139    }
140  } else {
141    LOG(ERROR) << "ExecuteWithPrivilegesAndGetPid: no line";
142  }
143
144  if (!pipe) {
145    fclose(*pipe_pointer);
146  }
147
148  if (pid) {
149    *pid = line_pid;
150  }
151
152  return status;
153}
154
155OSStatus ExecuteWithPrivilegesAndWait(AuthorizationRef authorization,
156                                      const char* tool_path,
157                                      AuthorizationFlags options,
158                                      const char** arguments,
159                                      FILE** pipe,
160                                      int* exit_status) {
161  pid_t pid;
162  OSStatus status = ExecuteWithPrivilegesAndGetPID(authorization,
163                                                   tool_path,
164                                                   options,
165                                                   arguments,
166                                                   pipe,
167                                                   &pid);
168  if (status != errAuthorizationSuccess) {
169    return status;
170  }
171
172  // exit_status may be NULL, but this function needs it.  In that case, use a
173  // local version.
174  int local_exit_status;
175  int* exit_status_pointer;
176  if (exit_status) {
177    exit_status_pointer = exit_status;
178  } else {
179    exit_status_pointer = &local_exit_status;
180  }
181
182  if (pid != -1) {
183    pid_t wait_result = HANDLE_EINTR(waitpid(pid, exit_status_pointer, 0));
184    if (wait_result != pid) {
185      PLOG(ERROR) << "waitpid";
186      *exit_status_pointer = -1;
187    }
188  } else {
189    *exit_status_pointer = -1;
190  }
191
192  return status;
193}
194
195}  // namespace mac
196}  // namespace base
197