Merge nucleic/vivid-glass-urchin-xoym into main
This commit is contained in:
@@ -50,6 +50,8 @@ public enum LocalNetworkPermission {
|
|||||||
case probeProducedNoReport
|
case probeProducedNoReport
|
||||||
/// A subnet argument is not an IPv4 address or CIDR block.
|
/// A subnet argument is not an IPv4 address or CIDR block.
|
||||||
case invalidSubnet(String)
|
case invalidSubnet(String)
|
||||||
|
/// A child process could not be started or waited on.
|
||||||
|
case spawnFailed(command: String, code: Int32)
|
||||||
|
|
||||||
public var description: String {
|
public var description: String {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -74,6 +76,8 @@ public enum LocalNetworkPermission {
|
|||||||
entries it cannot parse, which would leave the allowlist looking configured \
|
entries it cannot parse, which would leave the allowlist looking configured \
|
||||||
while granting nothing.
|
while granting nothing.
|
||||||
"""
|
"""
|
||||||
|
case .spawnFailed(let command, let code):
|
||||||
|
return "could not run \(command): \(String(cString: strerror(code))) (\(code))"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,27 +121,15 @@ public enum LocalNetworkPermission {
|
|||||||
|
|
||||||
let commands = LocalNetworkPolicy.writeCommandLines(subnets: subnets)
|
let commands = LocalNetworkPolicy.writeCommandLines(subnets: subnets)
|
||||||
|
|
||||||
for arguments in LocalNetworkPolicy.writeArguments(subnets: subnets) {
|
for (index, arguments) in LocalNetworkPolicy.writeArguments(subnets: subnets).enumerated() {
|
||||||
let sudoArguments = (allowPasswordPrompt ? [] : ["-n"]) + ["/usr/bin/defaults"] + arguments
|
let sudoArguments = (allowPasswordPrompt ? [] : ["-n"]) + ["/usr/bin/defaults"] + arguments
|
||||||
|
|
||||||
let process = Process()
|
let exitCode = try runInForeground("/usr/bin/sudo", sudoArguments)
|
||||||
process.executableURL = URL(fileURLWithPath: "/usr/bin/sudo")
|
guard exitCode == 0 else {
|
||||||
process.arguments = sudoArguments
|
|
||||||
// Stdio is deliberately inherited rather than piped. sudo reads the
|
|
||||||
// password from /dev/tty and would work either way, but its prompt
|
|
||||||
// and any "not in the sudoers file" complaint belong in front of
|
|
||||||
// the operator, not captured and paraphrased.
|
|
||||||
try process.run()
|
|
||||||
process.waitUntilExit()
|
|
||||||
|
|
||||||
guard process.terminationStatus == 0 else {
|
|
||||||
let index = LocalNetworkPolicy.writeArguments(subnets: subnets)
|
|
||||||
.firstIndex(of: arguments) ?? 0
|
|
||||||
if !allowPasswordPrompt {
|
if !allowPasswordPrompt {
|
||||||
throw PermissionError.needsPassword(commands: commands)
|
throw PermissionError.needsPassword(commands: commands)
|
||||||
}
|
}
|
||||||
throw PermissionError.writeFailed(
|
throw PermissionError.writeFailed(command: commands[index], exitCode: exitCode)
|
||||||
command: commands[index], exitCode: process.terminationStatus)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,11 +139,54 @@ public enum LocalNetworkPermission {
|
|||||||
/// Reboots the host. Only ever called from an explicit confirmation — the
|
/// Reboots the host. Only ever called from an explicit confirmation — the
|
||||||
/// allowlist is read at boot, so nothing else makes it take effect.
|
/// allowlist is read at boot, so nothing else makes it take effect.
|
||||||
public static func reboot() throws {
|
public static func reboot() throws {
|
||||||
let process = Process()
|
_ = try runInForeground("/usr/bin/sudo", ["/sbin/shutdown", "-r", "now"])
|
||||||
process.executableURL = URL(fileURLWithPath: "/usr/bin/sudo")
|
}
|
||||||
process.arguments = ["/sbin/shutdown", "-r", "now"]
|
|
||||||
try process.run()
|
/// Runs a command with this process's stdio *and its process group*, and
|
||||||
process.waitUntilExit()
|
/// returns its exit status.
|
||||||
|
///
|
||||||
|
/// The process group is the whole reason this is not `Foundation.Process`.
|
||||||
|
/// `Process` starts the child as its own process-group leader, so for the
|
||||||
|
/// controlling terminal the child is a *background* job — and the terminal
|
||||||
|
/// driver defends itself against those. `sudo`'s `tcsetattr` to turn echo
|
||||||
|
/// off raises `SIGTTOU` and fails, so the password is typed in the clear;
|
||||||
|
/// its read of the tty raises `SIGTTIN`, so Return never reaches `sudo` and
|
||||||
|
/// the line editor just echoes a newline. Both symptoms, one cause.
|
||||||
|
///
|
||||||
|
/// `posix_spawn` with no `POSIX_SPAWN_SETPGROUP` leaves the child in our
|
||||||
|
/// process group, which is the terminal's foreground group, so `sudo` gets
|
||||||
|
/// the terminal it expects. Stdio is inherited for the same reason it
|
||||||
|
/// always was: the prompt and any "not in the sudoers file" complaint
|
||||||
|
/// belong in front of the operator, not captured and paraphrased.
|
||||||
|
private static func runInForeground(_ executable: String, _ arguments: [String]) throws -> Int32
|
||||||
|
{
|
||||||
|
var argv: [UnsafeMutablePointer<CChar>?] = ([executable] + arguments).map { strdup($0) }
|
||||||
|
argv.append(nil)
|
||||||
|
var envp: [UnsafeMutablePointer<CChar>?] = ProcessInfo.processInfo.environment.map {
|
||||||
|
strdup("\($0.key)=\($0.value)")
|
||||||
|
}
|
||||||
|
envp.append(nil)
|
||||||
|
defer {
|
||||||
|
for pointer in argv { free(pointer) }
|
||||||
|
for pointer in envp { free(pointer) }
|
||||||
|
}
|
||||||
|
|
||||||
|
var pid: pid_t = 0
|
||||||
|
let spawned = posix_spawn(&pid, executable, nil, nil, argv, envp)
|
||||||
|
guard spawned == 0 else {
|
||||||
|
throw PermissionError.spawnFailed(command: executable, code: spawned)
|
||||||
|
}
|
||||||
|
|
||||||
|
var status: Int32 = 0
|
||||||
|
while waitpid(pid, &status, 0) < 0 {
|
||||||
|
guard errno == EINTR else {
|
||||||
|
throw PermissionError.spawnFailed(command: executable, code: errno)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WIFEXITED and friends are C macros, so Swift does not import them.
|
||||||
|
let terminatingSignal = status & 0x7F
|
||||||
|
return terminatingSignal == 0 ? (status >> 8) & 0xFF : 128 + terminatingSignal
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Interactive: the system prompt
|
// MARK: - Interactive: the system prompt
|
||||||
|
|||||||
Reference in New Issue
Block a user