macOS 中的特权文件复制(将辅助二进制文件安装到 /usr/local/bin)
Posted
技术标签:
【中文标题】macOS 中的特权文件复制(将辅助二进制文件安装到 /usr/local/bin)【英文标题】:Privileged file copy in macOS (Installing a helper binary to /usr/local/bin) 【发布时间】:2021-12-15 13:11:15 【问题描述】:我的主应用程序包中有一个辅助二进制文件mytool
,我需要将它复制到/usr/local/bin
。
现在bin
可能并不总是存在或具有写入权限,因此标准的NSWorkspace
调用将失败。我研究了不同的方法来做到这一点,但没有一个是令人满意的(或者我做错了)
为replaceFile
获得NSWorkspace.requestAuthorization
的授权
这似乎不起作用,因为在尝试用我的包中的文件“替换”/usr/local/bin/mytool
中的文件后,我仍然遇到权限错误。
通过AuthorizationCreate
手动获取授权。
这里的问题是 AuthorizationExecuteWithPrivileges
已被弃用(或者在我的情况下甚至在 Swift 中不可用),而 SMJobBless
似乎仅适用于运行时间更长的辅助进程。此外,SMJobBless
要求我的辅助工具拥有自己的 Info.plist
,因为它只是一个普通的二进制文件,所以它没有它
那么如何在 Swift 中执行特权文件复制?
PS:该应用没有沙盒化,所以NSOpenPanel
没有帮助。
【问题讨论】:
使用NSOpenPanel
并将directoryURL
指向/usr/local/bin。并要求用户选择它。
好吧,Kaleidoscope
例如在没有NSOpenPanel
的情况下执行此操作(这是一种可怕的体验)。所以必须有办法做到这一点......或者他们正在使用已弃用的 API。
向用户询问密码,然后使用Process
使用sudo 执行命令。处理 sudo 和密码的例子见this answer
大安全OOF。我真的不想要求 sudo 密码。 @ElTomato 不必支持 MAS。无论如何都不能在 MAS 版本中使用外部助手
我不知道万花筒这个家伙是什么。如果我没记错的话,BBEdit 使用NSOpenPanel
并让用户选择一个文件路径。我也是这样做的。
【参考方案1】:
好吧,我使用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($0)
defer args.forEach free($0)
let impl = unsafeBitCast(funcPtr, to: AuthorizationExecuteWithPrivilegesImpl.self)
return impl(authorization, path, [], args, nil)
【讨论】:
【参考方案2】:如果您想使用公共 API 执行此操作(意味着不使用已弃用的 API、调用 Apple 脚本、通过 Process 进行脱壳等),那么实现此目的的唯一方法是使用 SMJobBless
。无论好坏,这是 Apple 仍然官方支持的唯一选项。
如果您想在/usr/local/bin
中安装您的二进制文件,那么该二进制文件本身不需要有 Info.plist。你想创建一个不同的帮助工具,它可以通过SMJobBless
安装,可以将你的二进制文件复制到/usr/bin/local
。它将能够做到这一点,因为SMJobBless
安装的帮助工具始终以 root 身份运行。完成所有这些操作后,您可以使用 SMJobBless
安装的帮助工具自行卸载。不可否认,它相当复杂。
如果您确实想走这条路,请查看SwiftAuthorizationSample。
【讨论】:
这正是我不想做的,因为它比使用已弃用的 API 更糟糕。 当然是你的选择。从复杂性的角度来看,它肯定要高得多。更好的方式是它是 100% 支持的。在许多情况下,Apple 最终会完全删除已弃用的 API。以上是关于macOS 中的特权文件复制(将辅助二进制文件安装到 /usr/local/bin)的主要内容,如果未能解决你的问题,请参考以下文章