macOS 中的特权文件复制(将辅助二进制文件安装到 /usr/local/bin)

Privileged file copy in macOS (Installing a helper binary to /usr/local/bin)

我的主应用程序包中有一个辅助二进制文件 mytool,我需要将其复制到 /usr/local/bin

现在 bin 可能并不总是存在或具有写入权限,因此标准 NSWorkspace 调用将失败。我研究了不同的方法来做到这一点,但 none 令人满意(或者我做错了)

  1. 正在为 NSWorkspace.requestAuthorization

    获取 replaceFile 的授权

    这似乎不起作用,因为在尝试用我的包中的文件“替换”/usr/local/bin/mytool 中的文件后,我仍然遇到权限错误。

  2. 通过AuthorizationCreate手动获取授权。

    这里的问题是 AuthorizationExecuteWithPrivileges 已被弃用(或者在我的情况下甚至在 Swift 中不可用),并且 SMJobBless 似乎只适用于更长的时间 运行辅助进程。另外 SMJobBless 要求我的辅助工具有自己的 Info.plist,它没有,因为它只是一个普通的二进制文件

那么我如何设法在 Swift 中执行特权文件复制?

PS:该应用未被沙盒化,因此 NSOpenPanel 无济于事。

好吧,我使用 dlsym 挖出了已弃用的 API,因为除了手动询问用户密码外别无他法,除非已弃用,否则我不想这样做API 完全消失。

所以我现在要做的是使用 AuthorizationExecuteWithPrivileges 验证对 mytool --install 的调用,如下所示:

import Foundation
import Security

public struct Sudo {

    private typealias AuthorizationExecuteWithPrivilegesImpl = @convention(c) (
        AuthorizationRef,
        UnsafePointer<CChar>, // path
        AuthorizationFlags,
        UnsafePointer<UnsafeMutablePointer<CChar>?>, // args
        UnsafeMutablePointer<UnsafeMutablePointer<FILE>>?
    ) -> OSStatus

    /// This wraps the deprecated AuthorizationExecuteWithPrivileges
    /// and makes it accessible by Swift
    ///
    /// - Parameters:
    ///   - path: The executable path
    ///   - arguments: The executable arguments
    /// - Returns: `errAuthorizationSuccess` or an error code
    public static func run(path: String, arguments: [String]) -> Bool {
        var authRef: AuthorizationRef!
        var status = AuthorizationCreate(nil, nil, [], &authRef)

        guard status == errAuthorizationSuccess else { return false }
        defer { AuthorizationFree(authRef, [.destroyRights]) }

        var item = kAuthorizationRightExecute.withCString { name in
            AuthorizationItem(name: name, valueLength: 0, value: nil, flags: 0)
        }
        var rights = withUnsafeMutablePointer(to: &item) { ptr in
            AuthorizationRights(count: 1, items: ptr)
        }

        status = AuthorizationCopyRights(authRef, &rights, nil, [.interactionAllowed, .preAuthorize, .extendRights], nil)

        guard status == errAuthorizationSuccess else { return false }

        status = executeWithPrivileges(authorization: authRef, path: path, arguments: arguments)

        return status == errAuthorizationSuccess
    }

    private static func executeWithPrivileges(authorization: AuthorizationRef,
                                              path: String,
                                              arguments: [String]) -> OSStatus {
        let RTLD_DEFAULT = dlopen(nil, RTLD_NOW)
        guard let funcPtr = dlsym(RTLD_DEFAULT, "AuthorizationExecuteWithPrivileges") else { return -1 }
        let args = arguments.map { strdup([=10=]) }
        defer { args.forEach { free([=10=]) }}
        let impl = unsafeBitCast(funcPtr, to: AuthorizationExecuteWithPrivilegesImpl.self)
        return impl(authorization, path, [], args, nil)
    }
}

如果您想使用 public API 执行此操作(意味着不使用已弃用的 API、调用 Apple Script、通过 Process 等),那么实现此目的的唯一方法是使用 SMJobBless.不管是好是坏,这是 Apple 仍然官方支持的唯一选项。

如果您想在 /usr/local/bin 中安装您的二进制文件,那么该二进制文件本身不需要 Info.plist。您想要创建一个 不同的 帮助工具,它可以通过 SMJobBless 安装,可以将您的二进制文件复制到 /usr/bin/local。它将能够做到这一点,因为 SMJobBless 安装的辅助工具始终以 root 身份运行。完成所有这些操作后,您可以自行卸载使用 SMJobBless 安装的帮助工具。不可否认它相当复杂。

如果您确实想走这条路,请查看 SwiftAuthorizationSample