如何让非 root 父进程在 MacOS/X 下生成 root 子进程?

How to have a non-root parent process spawn a root child process under MacOS/X?

我有一个常规的旧 MacOS/X GUI 进程,由用户双击图标启动,因此 运行 该用户的权限。

我想要做的是让这个 GUI 进程生成一个子进程并通过其 stdin/stdout 与子进程通信。这一切都是可行和有效的。

诀窍是我希望子进程运行宁作为根,因为子进程需要做一些需要根访问的事情。

我预计这将需要打开一个对话框,要求用户输入他的管理员密码,这对于这个用例来说是可以的。

一种方法是让父进程 运行 像这样的 shell 命令:

/usr/bin/osascript -e 'do shell script "/bin/bash ./my_script.sh" with administrator privileges'

... 并且有 my_script.sh 运行 代表子进程的程序,我认为这会起作用,但是有没有更优雅的方法来完成这个(即不需要写一个 shell 脚本到磁盘的某个地方,并产生一个比神秘地说 "osascript wants to make changes")?

更用户友好的密码请求程序

通过研究 n.m 提到的 source code to cocoasudo,我终于能够让它工作了。

棘手的部分是意识到 AuthorizationExecuteWithPrivileges 给子进程一个 euid ("effective user ID") 0/root,所以它可以 事情需要 root 访问权限,但它不会更改子进程的 uid ("real user ID");它仍然设置为父进程的用户 ID,因此它实际上 不会被系统的其余部分视为根进程。

这个细节导致我的 shell-script 中的许多东西在以这种方式启动时无法正常工作;一方面,当使用 /bin/bash 执行脚本时,即使 whoami 表明它是以 root 身份执行的,但需要 root 访问权限的操作(例如将文件复制到 root-only-writeable 目录)仍然会失败。奇怪的是,/bin/sh 似乎没有遇到这个问题。

另一个主要障碍是我的脚本需要调用 launchctl load -w com.mycompany.myproduct.plist 来启动系统 LaunchDaemon,但是 launchctl 决定是否将服务安装为用户特定的服务还是系统-wide 基于真实用户 ID,因此我的服务将始终作为特定于用户的服务安装(运行 没有 root 访问权限,并且仅在当前用户登录时启动)而不是作为系统范围的服务运行 作为 root。

经过大量猜测和谷歌搜索后,我被引导至 Apple Tech Note TN2083 底部的 "Hints and Tips" 部分,其中指出:

IMPORTANT: For this to work properly, you must run launchctl as root (both its EUID and RUID must be 0). This ensures that launchctl communicates with the primary instance of launchd.

...并且由于(据我所知)没有系统提供的命令行工具可以为我调用 setuid(0),我不得不在 C:[=21 中编写自己的小脚本启动器工具=]

int main(int argc, char ** argv)
{
   if (argc >= 2)
   {
      if (setuid(0) == 0)   // make us really root, not just "effectively root"
      {
         system(argv[1]);  // then run the specified script file
         return 0;
      }
      else perror("setuid");
   }
   else printf("Usage:  setuid_script_launcher <path_to_script>\n");

   return 10;
}

这比我想象的要多得多,但现在一切正常。我在这里描述它是为了让下一个人免于痛苦。