80 lines
2.5 KiB
Swift
80 lines
2.5 KiB
Swift
import CoreWLAN
|
|
import Foundation
|
|
|
|
private struct AssociationRequest: Decodable {
|
|
let ssid: String
|
|
let password: String
|
|
}
|
|
|
|
private struct AssociationResponse: Encodable {
|
|
let ok: Bool
|
|
let alreadyAssociated: Bool?
|
|
let reasonCode: String?
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case ok
|
|
case alreadyAssociated = "already_associated"
|
|
case reasonCode = "reason_code"
|
|
}
|
|
}
|
|
|
|
private func emit(_ response: AssociationResponse, exitCode: Int32) -> Never {
|
|
let encoder = JSONEncoder()
|
|
if let data = try? encoder.encode(response) {
|
|
FileHandle.standardOutput.write(data)
|
|
}
|
|
exit(exitCode)
|
|
}
|
|
|
|
private let input = FileHandle.standardInput.readDataToEndOfFile()
|
|
guard input.count <= 1024 else {
|
|
emit(
|
|
AssociationResponse(ok: false, alreadyAssociated: nil, reasonCode: "request-too-large"),
|
|
exitCode: 1
|
|
)
|
|
}
|
|
|
|
do {
|
|
let request = try JSONDecoder().decode(AssociationRequest.self, from: input)
|
|
guard let ssidData = request.ssid.data(using: .utf8),
|
|
(1 ... 32).contains(ssidData.count),
|
|
let passwordData = request.password.data(using: .utf8),
|
|
(1 ... 64).contains(passwordData.count)
|
|
else {
|
|
emit(
|
|
AssociationResponse(ok: false, alreadyAssociated: nil, reasonCode: "credential-bounds"),
|
|
exitCode: 1
|
|
)
|
|
}
|
|
|
|
guard let interface = CWWiFiClient.shared().interface() else {
|
|
emit(
|
|
AssociationResponse(ok: false, alreadyAssociated: nil, reasonCode: "wifi-interface-unavailable"),
|
|
exitCode: 1
|
|
)
|
|
}
|
|
|
|
if interface.ssid() == request.ssid {
|
|
emit(AssociationResponse(ok: true, alreadyAssociated: true, reasonCode: nil), exitCode: 0)
|
|
}
|
|
|
|
let networks = try interface.scanForNetworks(withSSID: ssidData)
|
|
guard let network = networks.first(where: { $0.ssid == request.ssid }) else {
|
|
emit(
|
|
AssociationResponse(ok: false, alreadyAssociated: nil, reasonCode: "network-not-found"),
|
|
exitCode: 1
|
|
)
|
|
}
|
|
|
|
try interface.associate(to: network, password: request.password)
|
|
// CoreWLAN's synchronous association call throws on failure. Reading the
|
|
// current SSID again would require Location authorization on recent macOS
|
|
// versions and could turn a successful association into a false negative.
|
|
emit(AssociationResponse(ok: true, alreadyAssociated: false, reasonCode: nil), exitCode: 0)
|
|
} catch {
|
|
emit(
|
|
AssociationResponse(ok: false, alreadyAssociated: nil, reasonCode: "corewlan-error"),
|
|
exitCode: 1
|
|
)
|
|
}
|