forked from jacopo-j/pam-watchid
-
Notifications
You must be signed in to change notification settings - Fork 0
/
watchid-pam-extension.swift
101 lines (77 loc) · 3.21 KB
/
watchid-pam-extension.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import LocalAuthentication
// MARK: (Re)define PAM constants here so we don't need to import .h files.
private let PAM_SUCCESS = 0
private let PAM_AUTH_ERR = 9
private let PAM_IGNORE = 25
private let DEFAULT_REASON = "perform an action that requires authentication"
public typealias vchar = UnsafeMutablePointer<UnsafeMutablePointer<Int8>>
public typealias pam_handler_t = UnsafeRawPointer
// MARK: Biometric (touchID) authentication
@_silgen_name("pam_sm_authenticate")
public func pam_sm_authenticate(pamh: pam_handler_t, flags: Int, argc: Int, argv: vchar) -> Int {
if (ProcessInfo.processInfo.environment["SSH_TTY"] != nil) {
return PAM_IGNORE;
}
let sudoArguments = ProcessInfo.processInfo.arguments
if sudoArguments.contains("-A") || sudoArguments.contains("--askpass") {
return PAM_IGNORE
}
let arguments = parseArguments(argc: argc, argv: argv)
var reason = arguments["reason"] ?? DEFAULT_REASON
reason = reason.isEmpty ? DEFAULT_REASON : reason
let policy = LAPolicy.deviceOwnerAuthenticationIgnoringUserID
let context = LAContext()
if !context.canEvaluatePolicy(policy, error: nil) {
return PAM_IGNORE
}
let semaphore = DispatchSemaphore(value: 0)
var result = PAM_AUTH_ERR
context.evaluatePolicy(policy, localizedReason: reason) { success, error in
defer { semaphore.signal() }
if let error = error {
fputs("\(error.localizedDescription)\n", stderr)
}
result = success ? PAM_SUCCESS : PAM_AUTH_ERR
}
semaphore.wait()
return result
}
private func parseArguments(argc: Int, argv: vchar) -> [String: String] {
var parsed = [String: String]()
let arguments = (0 ..< argc)
.map { String(cString: argv[$0]) }
.joined(separator: " ")
let regex = try? NSRegularExpression(pattern: "[^\\s\"']+|\"([^\"]*)\"|'([^']*)'",
options: .dotMatchesLineSeparators)
let matches = regex?.matches(in: arguments, options: .withoutAnchoringBounds,
range: NSRange(location: 0, length: arguments.count))
let nsArguments = arguments as NSString
let groups = matches?
.map { nsArguments.substring(with: $0.range) }
.map { ($0 as String).trimmingCharacters(in: CharacterSet(charactersIn: "\"'")) }
for argument in groups ?? [] {
let pieces = argument.components(separatedBy: "=")
if pieces.count == 2, let key = pieces.first, let value = pieces.last {
parsed[key] = value
}
}
return parsed
}
private extension LAPolicy {
static var deviceOwnerAuthenticationIgnoringUserID: LAPolicy {
return .deviceOwnerAuthenticationWithBiometricsOrWatch
}
}
// MARK: - Ignored (unhandled) PAM events
@_silgen_name("pam_sm_chauthtok")
public func pam_sm_chauthtok(pamh: pam_handler_t, flags: Int, argc: Int, argv: vchar) -> Int {
return PAM_IGNORE
}
@_silgen_name("pam_sm_setcred")
public func pam_sm_setcred(pamh: pam_handler_t, flags: Int, argc: Int, argv: vchar) -> Int {
return PAM_IGNORE
}
@_silgen_name("pam_sm_acct_mgmt")
public func pam_sm_acct_mgmt(pamh: pam_handler_t, flags: Int, argc: Int, argv: vchar) -> Int {
return PAM_IGNORE
}