I have a helper binary mytool
inside my main app bundle that I need to copy to /usr/local/bin
.
Now bin
might not always exist or have write access, so the standard NSWorkspace
calls will fail on it. I looked into different ways to do this, but none are satisfactory (or I am doing it wrong)
Getting an authorization for
replaceFile
forNSWorkspace.requestAuthorization
This does not seem to work, as I still get a privileges error after trying to "replace" the file in
/usr/local/bin/mytool
with the one from my bundle.Manually getting Authorization via
AuthorizationCreate
.The problem here is that
AuthorizationExecuteWithPrivileges
is deprecated (or in my case not even available in Swift), andSMJobBless
seems to be only for longer running helper processes. AlsoSMJobBless
requires my helper tool to have anInfo.plist
of its own, which it doesn't have since its just a plain binary
So how do I manage to perform a privileged file copy in Swift?
PS: The app is not sandboxed, so NSOpenPanel
does not help.
CodePudding user response:
Well I dug out the deprecated API using dlsym
, because there is simply no other way besides asking the user manually for his password, which I don't want to do unless the deprecated API disappears entirely.
So what I do now is authenticate a call to mytool --install
using AuthorizationExecuteWithPrivileges
like this:
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)
}
}
CodePudding user response:
If you want to do this using public APIs (meaning not using deprecated APIs, invoking Apple Script, shelling out via Process, etc.) then the only way to achieve this is using SMJobBless
. For better or worse, that's the only option still officially supported by Apple.
If you want to install your binary in /usr/local/bin
then that binary itself doesn't need to have an Info.plist. You'd want to create a different helper tool which would be installed via SMJobBless
that could copy your binary to /usr/bin/local
. It will be able to do that because a helper tool installed by SMJobBless
always runs as root. Once you're done with all of this you could have the helper tool you installed with SMJobBless
uninstall itself. No denying it's rather involved.
If you do want to go down this route, take a look at SwiftAuthorizationSample.