]>
Commit | Line | Data |
---|---|---|
427c49bc A |
1 | /* Copyright (c) 2012 Apple Inc. All rights reserved. */ |
2 | ||
3 | #include "engine.h" | |
4 | #include "rule.h" | |
5 | #include "authitems.h" | |
6 | #include "authtoken.h" | |
7 | #include "agent.h" | |
8 | #include "process.h" | |
9 | #include "debugging.h" | |
10 | #include "server.h" | |
11 | #include "credential.h" | |
12 | #include "session.h" | |
13 | #include "mechanism.h" | |
14 | #include "authutilities.h" | |
15 | #include "ccaudit.h" | |
16 | #include "connection.h" | |
17 | ||
18 | #include <pwd.h> | |
19 | #include <Security/checkpw.h> | |
20 | int checkpw_internal( const struct passwd *pw, const char* password ); | |
21 | ||
22 | #include <Security/AuthorizationTags.h> | |
23 | #include <Security/AuthorizationTagsPriv.h> | |
24 | #include <Security/AuthorizationPlugin.h> | |
25 | #include <sandbox.h> | |
26 | ||
27 | static void _set_process_hints(auth_items_t, process_t); | |
28 | static void _set_process_immutable_hints(auth_items_t, process_t); | |
29 | static void _set_auth_token_hints(auth_items_t, auth_token_t); | |
30 | static OSStatus _evaluate_user_credential_for_rule(engine_t, credential_t, rule_t, bool, bool, enum Reason *); | |
31 | static void _engine_set_credential(engine_t, credential_t, bool); | |
32 | static OSStatus _evaluate_rule(engine_t, rule_t); | |
33 | ||
34 | enum { | |
35 | kEngineHintsFlagTemporary = (1 << 30) | |
36 | }; | |
37 | ||
38 | #pragma mark - | |
39 | #pragma mark engine creation | |
40 | ||
41 | struct _engine_s { | |
42 | __AUTH_BASE_STRUCT_HEADER__; | |
43 | ||
44 | connection_t conn; | |
45 | process_t proc; | |
46 | auth_token_t auth; | |
47 | ||
48 | AuthorizationFlags flags; | |
49 | auth_items_t hints; | |
50 | auth_items_t context; | |
51 | auth_items_t sticky_context; | |
52 | auth_items_t immutable_hints; | |
53 | ||
54 | auth_rights_t grantedRights; | |
55 | ||
56 | enum Reason reason; | |
57 | int32_t tries; | |
58 | ||
59 | CFAbsoluteTime now; | |
60 | ||
61 | credential_t sessionCredential; | |
62 | CFMutableSetRef credentials; | |
63 | CFMutableSetRef effectiveCredentials; | |
64 | ||
65 | CFMutableDictionaryRef mechanism_agents; | |
66 | ||
67 | // set only in engine_authorize | |
68 | const char * currentRightName; // weak ref | |
69 | rule_t currentRule; // weak ref | |
70 | ||
71 | rule_t authenticateRule; | |
72 | ||
73 | bool dismissed; | |
74 | }; | |
75 | ||
76 | static void | |
77 | _engine_finalizer(CFTypeRef value) | |
78 | { | |
79 | engine_t engine = (engine_t)value; | |
80 | ||
81 | CFReleaseSafe(engine->mechanism_agents); | |
82 | CFReleaseSafe(engine->conn); | |
83 | CFReleaseSafe(engine->auth); | |
84 | CFReleaseSafe(engine->hints); | |
85 | CFReleaseSafe(engine->context); | |
86 | CFReleaseSafe(engine->immutable_hints); | |
87 | CFReleaseSafe(engine->sticky_context); | |
88 | CFReleaseSafe(engine->grantedRights); | |
89 | CFReleaseSafe(engine->sessionCredential); | |
90 | CFReleaseSafe(engine->credentials); | |
91 | CFReleaseSafe(engine->effectiveCredentials); | |
92 | CFReleaseSafe(engine->authenticateRule); | |
93 | } | |
94 | ||
95 | AUTH_TYPE_INSTANCE(engine, | |
96 | .init = NULL, | |
97 | .copy = NULL, | |
98 | .finalize = _engine_finalizer, | |
99 | .equal = NULL, | |
100 | .hash = NULL, | |
101 | .copyFormattingDesc = NULL, | |
102 | .copyDebugDesc = NULL | |
103 | ); | |
104 | ||
105 | static CFTypeID engine_get_type_id() { | |
106 | static CFTypeID type_id = _kCFRuntimeNotATypeID; | |
107 | static dispatch_once_t onceToken; | |
108 | ||
109 | dispatch_once(&onceToken, ^{ | |
110 | type_id = _CFRuntimeRegisterClass(&_auth_type_engine); | |
111 | }); | |
112 | ||
113 | return type_id; | |
114 | } | |
115 | ||
116 | engine_t | |
117 | engine_create(connection_t conn, auth_token_t auth) | |
118 | { | |
119 | engine_t engine = NULL; | |
120 | require(conn != NULL, done); | |
121 | require(auth != NULL, done); | |
122 | ||
123 | engine = (engine_t)_CFRuntimeCreateInstance(kCFAllocatorDefault, engine_get_type_id(), AUTH_CLASS_SIZE(engine), NULL); | |
124 | require(engine != NULL, done); | |
125 | ||
126 | engine->conn = (connection_t)CFRetain(conn); | |
127 | engine->proc = connection_get_process(conn); | |
128 | engine->auth = (auth_token_t)CFRetain(auth); | |
129 | ||
130 | engine->hints = auth_items_create(); | |
131 | engine->context = auth_items_create(); | |
132 | engine->immutable_hints = auth_items_create(); | |
133 | engine->sticky_context = auth_items_create(); | |
134 | _set_process_hints(engine->hints, engine->proc); | |
135 | _set_process_immutable_hints(engine->immutable_hints, engine->proc); | |
136 | _set_auth_token_hints(engine->hints, auth); | |
137 | ||
138 | engine->grantedRights = auth_rights_create(); | |
139 | ||
140 | engine->reason = noReason; | |
141 | ||
142 | engine->now = CFAbsoluteTimeGetCurrent(); | |
143 | ||
144 | session_update(auth_token_get_session(engine->auth)); | |
145 | engine->sessionCredential = credential_create(session_get_uid(auth_token_get_session(engine->auth))); | |
146 | engine->credentials = CFSetCreateMutable(kCFAllocatorDefault, 0, &kCFTypeSetCallBacks); | |
147 | engine->effectiveCredentials = CFSetCreateMutable(kCFAllocatorDefault, 0, &kCFTypeSetCallBacks); | |
148 | ||
149 | session_credentials_iterate(auth_token_get_session(engine->auth), ^bool(credential_t cred) { | |
150 | CFSetAddValue(engine->effectiveCredentials, cred); | |
151 | return true; | |
152 | }); | |
153 | ||
154 | auth_token_credentials_iterate(engine->auth, ^bool(credential_t cred) { | |
155 | // we added all session credentials already now just add all previously acquired credentials | |
156 | if (!credential_get_shared(cred)) { | |
157 | CFSetAddValue(engine->credentials, cred); | |
158 | } | |
159 | return true; | |
160 | }); | |
161 | ||
162 | engine->mechanism_agents = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); | |
163 | ||
164 | done: | |
165 | return engine; | |
166 | } | |
167 | ||
168 | #pragma mark - | |
169 | #pragma mark agent hints | |
170 | ||
171 | void | |
172 | _set_process_hints(auth_items_t hints, process_t proc) | |
173 | { | |
174 | // process information | |
175 | RequestorType type = bundle; | |
176 | auth_items_set_data(hints, AGENT_HINT_CLIENT_TYPE, &type, sizeof(type)); | |
177 | auth_items_set_int(hints, AGENT_HINT_CLIENT_PID, process_get_pid(proc)); | |
178 | auth_items_set_uint(hints, AGENT_HINT_CLIENT_UID, process_get_uid(proc)); | |
179 | } | |
180 | ||
181 | void | |
182 | _set_process_immutable_hints(auth_items_t immutable_hints, process_t proc) | |
183 | { | |
184 | // process information - immutable | |
185 | auth_items_set_bool(immutable_hints, AGENT_HINT_PROCESS_SIGNED, process_apple_signed(proc)); | |
186 | } | |
187 | ||
188 | void | |
189 | _set_auth_token_hints(auth_items_t hints, auth_token_t auth) | |
190 | { | |
191 | auth_items_set_string(hints, AGENT_HINT_CLIENT_PATH, auth_token_get_code_url(auth)); | |
192 | auth_items_set_int(hints, AGENT_HINT_CREATOR_PID, auth_token_get_pid(auth)); | |
193 | const audit_info_s * info = auth_token_get_audit_info(auth); | |
194 | auth_items_set_data(hints, AGENT_HINT_CREATOR_AUDIT_TOKEN, &info->opaqueToken, sizeof(info->opaqueToken)); | |
195 | } | |
196 | ||
197 | static void | |
198 | _set_right_hints(auth_items_t hints, const char * right) | |
199 | { | |
200 | auth_items_set_string(hints, AGENT_HINT_AUTHORIZE_RIGHT, right); | |
201 | } | |
202 | ||
203 | static void | |
204 | _set_rule_hints(auth_items_t hints, rule_t rule) | |
205 | { | |
206 | auth_items_set_string(hints, AGENT_HINT_AUTHORIZE_RULE, rule_get_name(rule)); | |
207 | const char * group = rule_get_group(rule); | |
208 | if (rule_get_class(rule) == RC_USER && group != NULL) { | |
209 | auth_items_set_string(hints, AGENT_HINT_REQUIRE_USER_IN_GROUP, group); | |
210 | } else { | |
211 | auth_items_remove(hints, AGENT_HINT_REQUIRE_USER_IN_GROUP); | |
212 | } | |
213 | } | |
214 | ||
215 | static void | |
216 | _set_localization_hints(authdb_connection_t dbconn, auth_items_t hints, rule_t rule) | |
217 | { | |
218 | char * key = calloc(1u, 128); | |
219 | ||
220 | authdb_step(dbconn, "SELECT lang,value FROM prompts WHERE r_id = ?", ^(sqlite3_stmt *stmt) { | |
221 | sqlite3_bind_int64(stmt, 1, rule_get_id(rule)); | |
222 | }, ^bool(auth_items_t data) { | |
223 | snprintf(key, 128, "%s%s", kAuthorizationRuleParameterDescription, auth_items_get_string(data, "lang")); | |
224 | auth_items_set_string(hints, key, auth_items_get_string(data, "value")); | |
225 | auth_items_set_flags(hints, key, kEngineHintsFlagTemporary); | |
226 | return true; | |
227 | }); | |
228 | ||
229 | authdb_step(dbconn, "SELECT lang,value FROM buttons WHERE r_id = ?", ^(sqlite3_stmt *stmt) { | |
230 | sqlite3_bind_int64(stmt, 1, rule_get_id(rule)); | |
231 | }, ^bool(auth_items_t data) { | |
232 | snprintf(key, 128, "%s%s", kAuthorizationRuleParameterButton, auth_items_get_string(data, "lang")); | |
233 | auth_items_set_string(hints, key, auth_items_get_string(data, "value")); | |
234 | auth_items_set_flags(hints, key, kEngineHintsFlagTemporary); | |
235 | return true; | |
236 | }); | |
237 | ||
238 | free_safe(key); | |
239 | } | |
240 | ||
241 | static void | |
242 | _set_session_hints(engine_t engine, rule_t rule) | |
243 | { | |
244 | LOGV("engine[%i]: ** prepare agent hints for rule %s", connection_get_pid(engine->conn), rule_get_name(rule)); | |
245 | if (_evaluate_user_credential_for_rule(engine, engine->sessionCredential, rule, true, true, NULL) == errAuthorizationSuccess) { | |
246 | const char * tmp = credential_get_name(engine->sessionCredential); | |
247 | if (tmp != NULL) { | |
248 | auth_items_set_string(engine->hints, AGENT_HINT_SUGGESTED_USER, tmp); | |
249 | } | |
250 | tmp = credential_get_realname(engine->sessionCredential); | |
251 | if (tmp != NULL) { | |
252 | auth_items_set_string(engine->hints, AGENT_HINT_SUGGESTED_USER_LONG, tmp); | |
253 | } | |
254 | } else { | |
255 | auth_items_remove(engine->hints, AGENT_HINT_SUGGESTED_USER); | |
256 | auth_items_remove(engine->hints, AGENT_HINT_SUGGESTED_USER_LONG); | |
257 | } | |
258 | } | |
259 | ||
260 | #pragma mark - | |
261 | #pragma mark right processing | |
262 | ||
263 | static OSStatus | |
264 | _evaluate_credential_for_rule(engine_t engine, credential_t cred, rule_t rule, bool ignoreShared, bool sessionOwner, enum Reason * reason) | |
265 | { | |
266 | if (auth_token_least_privileged(engine->auth)) { | |
267 | if (credential_is_right(cred) && credential_get_valid(cred) && _compare_string(engine->currentRightName, credential_get_name(cred))) { | |
268 | if (!ignoreShared) { | |
269 | if (!rule_get_shared(rule) && credential_get_shared(cred)) { | |
270 | LOGV("engine[%i]: - shared right %s (does NOT satisfy rule)", connection_get_pid(engine->conn), credential_get_name(cred)); | |
271 | if (reason) { *reason = unknownReason; } | |
272 | return errAuthorizationDenied; | |
273 | } | |
274 | } | |
275 | ||
276 | return errAuthorizationSuccess; | |
277 | } else { | |
278 | if (reason) { *reason = unknownReason; } | |
279 | return errAuthorizationDenied; | |
280 | } | |
281 | } else { | |
282 | return _evaluate_user_credential_for_rule(engine,cred,rule,ignoreShared,sessionOwner, reason); | |
283 | } | |
284 | } | |
285 | ||
286 | static OSStatus | |
287 | _evaluate_user_credential_for_rule(engine_t engine, credential_t cred, rule_t rule, bool ignoreShared, bool sessionOwner, enum Reason * reason) | |
288 | { | |
289 | const char * cred_label = sessionOwner ? "session owner" : "credential"; | |
290 | LOGV("engine[%i]: - validating %s%s %s (%i) for %s", connection_get_pid(engine->conn), | |
291 | credential_get_shared(cred) ? "shared " : "", | |
292 | cred_label, | |
293 | credential_get_name(cred), | |
294 | credential_get_uid(cred), | |
295 | rule_get_name(rule)); | |
296 | ||
297 | if (rule_get_class(rule) != RC_USER) { | |
298 | LOGV("engine[%i]: - invalid rule class %i (denied)", connection_get_pid(engine->conn), rule_get_class(rule)); | |
299 | return errAuthorizationDenied; | |
300 | } | |
301 | ||
302 | if (credential_get_valid(cred) != true) { | |
303 | LOGV("engine[%i]: - %s %i invalid (does NOT satisfy rule)", connection_get_pid(engine->conn), cred_label, credential_get_uid(cred)); | |
304 | if (reason) { *reason = invalidPassphrase; } | |
305 | return errAuthorizationDenied; | |
306 | } | |
307 | ||
308 | if (engine->now - credential_get_creation_time(cred) > rule_get_timeout(rule)) { | |
309 | LOGV("engine[%i]: - %s %i expired '%f > %lli' (does NOT satisfy rule)", connection_get_pid(engine->conn), cred_label, credential_get_uid(cred), | |
310 | (engine->now - credential_get_creation_time(cred)), rule_get_timeout(rule)); | |
311 | if (reason) { *reason = unknownReason; } | |
312 | return errAuthorizationDenied; | |
313 | } | |
314 | ||
315 | ||
316 | if (!ignoreShared) { | |
317 | if (!rule_get_shared(rule) && credential_get_shared(cred)) { | |
318 | LOGV("engine[%i]: - shared %s %i (does NOT satisfy rule)", connection_get_pid(engine->conn), cred_label, credential_get_uid(cred)); | |
319 | if (reason) { *reason = unknownReason; } | |
320 | return errAuthorizationDenied; | |
321 | } | |
322 | } | |
323 | ||
324 | if (credential_get_uid(cred) == 0) { | |
325 | LOGV("engine[%i]: - %s %i has uid 0 (does satisfy rule)", connection_get_pid(engine->conn), cred_label, credential_get_uid(cred)); | |
326 | return errAuthorizationSuccess; | |
327 | } | |
328 | ||
329 | if (rule_get_session_owner(rule)) { | |
330 | if (credential_get_uid(cred) == session_get_uid(auth_token_get_session(engine->auth))) { | |
331 | LOGV("engine[%i]: - %s %i is session owner (does satisfy rule)", connection_get_pid(engine->conn), cred_label, credential_get_uid(cred)); | |
332 | return errAuthorizationSuccess; | |
333 | } | |
334 | } | |
335 | ||
336 | if (rule_get_group(rule) != NULL) { | |
337 | do | |
338 | { | |
339 | // This allows testing a group modifier without prompting the user | |
340 | // When (authenticate-user = false) we are just testing the creator uid. | |
341 | // If a group modifier is enabled (RuleFlagEntitledAndGroup | RuleFlagVPNEntitledAndGroup) | |
342 | // we want to skip the creator uid group check. | |
343 | // group modifiers are checked early during the evaluation in _check_entitlement_for_rule | |
344 | if (!rule_get_authenticate_user(rule)) { | |
345 | if (rule_check_flags(rule, RuleFlagEntitledAndGroup | RuleFlagVPNEntitledAndGroup)) { | |
346 | break; | |
347 | } | |
348 | } | |
349 | ||
350 | if (credential_check_membership(cred, rule_get_group(rule))) { | |
351 | LOGV("engine[%i]: - %s %i is member of group %s (does satisfy rule)", connection_get_pid(engine->conn), cred_label, credential_get_uid(cred), rule_get_group(rule)); | |
352 | return errAuthorizationSuccess; | |
353 | } else { | |
354 | if (reason) { *reason = userNotInGroup; } | |
355 | } | |
356 | } while (0); | |
357 | } else if (rule_get_session_owner(rule)) { // rule asks only if user is the session owner | |
358 | if (reason) { *reason = unacceptableUser; } | |
359 | } | |
360 | ||
361 | LOGV("engine[%i]: - %s %i (does NOT satisfy rule)", connection_get_pid(engine->conn), cred_label, credential_get_uid(cred)); | |
362 | return errAuthorizationDenied; | |
363 | } | |
364 | ||
365 | static agent_t | |
366 | _get_agent(engine_t engine, mechanism_t mech, bool create, bool firstMech) | |
367 | { | |
368 | agent_t agent = (agent_t)CFDictionaryGetValue(engine->mechanism_agents, mech); | |
369 | if (create && !agent) { | |
370 | agent = agent_create(engine, mech, engine->auth, engine->proc, firstMech); | |
371 | if (agent) { | |
372 | CFDictionaryAddValue(engine->mechanism_agents, mech, agent); | |
373 | CFReleaseSafe(agent); | |
374 | } | |
375 | } | |
376 | return agent; | |
377 | } | |
378 | ||
379 | static uint64_t | |
380 | _evaluate_builtin_mechanism(engine_t engine, mechanism_t mech) | |
381 | { | |
382 | uint64_t result = kAuthorizationResultDeny; | |
383 | ||
384 | switch (mechanism_get_type(mech)) { | |
385 | case kMechanismTypeEntitled: | |
386 | if (auth_token_has_entitlement_for_right(engine->auth, engine->currentRightName)) { | |
387 | result = kAuthorizationResultAllow; | |
388 | } | |
389 | break; | |
390 | default: | |
391 | break; | |
392 | } | |
393 | ||
394 | return result; | |
395 | } | |
396 | ||
397 | ||
398 | static OSStatus | |
399 | _evaluate_mechanisms(engine_t engine, CFArrayRef mechanisms) | |
400 | { | |
401 | uint64_t result = kAuthorizationResultAllow; | |
402 | ccaudit_t ccaudit = ccaudit_create(engine->proc, engine->auth, AUE_ssauthmech); | |
403 | auth_items_t context = auth_items_create(); | |
404 | auth_items_t hints = auth_items_create(); | |
405 | ||
406 | auth_items_copy(context, engine->context); | |
407 | auth_items_copy(hints, engine->hints); | |
408 | auth_items_copy(context, engine->sticky_context); | |
409 | ||
410 | CFIndex count = CFArrayGetCount(mechanisms); | |
411 | for (CFIndex i = 0; i < count; i++) { | |
412 | mechanism_t mech = (mechanism_t)CFArrayGetValueAtIndex(mechanisms, i); | |
413 | ||
414 | if (mechanism_get_type(mech)) { | |
415 | LOGV("engine[%i]: running builtin mechanism %s (%li of %li)", connection_get_pid(engine->conn), mechanism_get_string(mech), i+1, count); | |
416 | result = _evaluate_builtin_mechanism(engine, mech); | |
417 | } else { | |
418 | agent_t agent = _get_agent(engine, mech, true, i == 0); | |
419 | require_action(agent != NULL, done, result = kAuthorizationResultUndefined; LOGE("engine[%i]: error creating mechanism agent", connection_get_pid(engine->conn))); | |
420 | LOGV("engine[%i]: running mechanism %s (%li of %li)", connection_get_pid(engine->conn), mechanism_get_string(agent_get_mechanism(agent)), i+1, count); | |
421 | result = agent_run(agent, hints, context, engine->immutable_hints); | |
422 | ||
423 | auth_items_copy(context, agent_get_context(agent)); | |
424 | auth_items_copy(hints, agent_get_hints(agent)); | |
425 | ||
426 | bool interrupted = false; | |
427 | for (CFIndex i2 = 0; i2 != i; i2++) { | |
428 | agent_t agent2 = _get_agent(engine, (mechanism_t)CFArrayGetValueAtIndex(mechanisms, i2), false, i == 0); | |
429 | if (agent_get_state(agent2) == interrupting) { | |
430 | agent_deactivate(agent); | |
431 | interrupted = true; | |
432 | i = i2 - 1; | |
433 | char * buf = NULL; | |
434 | asprintf(&buf, "evaluation interrupted by %s; restarting evaluation there", mechanism_get_string(agent_get_mechanism(agent2))); | |
435 | ccaudit_log_mechanism(ccaudit, engine->currentRightName, mechanism_get_string(agent_get_mechanism(agent2)), kAuthorizationResultAllow, buf); | |
436 | free_safe(buf); | |
437 | auth_items_copy(context, agent_get_context(agent2)); | |
438 | auth_items_copy(hints, agent_get_hints(agent2)); | |
439 | break; | |
440 | } | |
441 | } | |
4d3cab3d A |
442 | |
443 | // Empty token name means that token doesn't exist (e.g. SC was removed). | |
444 | // Remove empty token name from hints for UI drawing logic. | |
445 | const char * token_name = auth_items_get_string(hints, AGENT_HINT_TOKEN_NAME); | |
446 | if (token_name && strlen(token_name) == 0) { | |
447 | auth_items_remove(hints, AGENT_HINT_TOKEN_NAME); | |
448 | } | |
427c49bc A |
449 | |
450 | if (interrupted) { | |
451 | LOGV("engine[%i]: mechanisms interrupted", connection_get_pid(engine->conn)); | |
452 | enum Reason reason = worldChanged; | |
453 | auth_items_set_data(hints, AGENT_HINT_RETRY_REASON, &reason, sizeof(reason)); | |
454 | result = kAuthorizationResultAllow; | |
455 | _cf_dictionary_iterate(engine->mechanism_agents, ^bool(CFTypeRef key __attribute__((__unused__)), CFTypeRef value) { | |
456 | agent_t tempagent = (agent_t)value; | |
457 | agent_clear_interrupt(tempagent); | |
458 | return true; | |
459 | }); | |
460 | } | |
461 | } | |
462 | ||
463 | if (result == kAuthorizationResultAllow) { | |
464 | ccaudit_log_mechanism(ccaudit, engine->currentRightName, mechanism_get_string(mech), kAuthorizationResultAllow, NULL); | |
465 | } else { | |
466 | ccaudit_log_mechanism(ccaudit, engine->currentRightName, mechanism_get_string(mech), (uint32_t)result, NULL); | |
467 | break; | |
468 | } | |
469 | } | |
470 | ||
471 | done: | |
472 | if ((result == kAuthorizationResultUserCanceled) || (result == kAuthorizationResultAllow)) { | |
473 | // only make non-sticky context values available externally | |
474 | auth_items_set_flags(context, kAuthorizationEnvironmentPassword, kAuthorizationContextFlagVolatile); | |
475 | auth_items_copy_with_flags(engine->context, context, kAuthorizationContextFlagExtractable | kAuthorizationContextFlagVolatile); | |
476 | } else if (result == kAuthorizationResultDeny) { | |
477 | auth_items_clear(engine->sticky_context); | |
478 | // save off sticky values in context | |
479 | auth_items_copy_with_flags(engine->sticky_context, context, kAuthorizationContextFlagSticky); | |
480 | } | |
481 | ||
482 | CFReleaseSafe(ccaudit); | |
483 | CFReleaseSafe(context); | |
484 | CFReleaseSafe(hints); | |
485 | ||
486 | switch(result) | |
487 | { | |
488 | case kAuthorizationResultDeny: | |
489 | return errAuthorizationDenied; | |
490 | case kAuthorizationResultUserCanceled: | |
491 | return errAuthorizationCanceled; | |
492 | case kAuthorizationResultAllow: | |
493 | return errAuthorizationSuccess; | |
494 | case kAuthorizationResultUndefined: | |
495 | return errAuthorizationInternal; | |
496 | default: | |
497 | { | |
498 | LOGV("engine[%i]: unexpected error result", connection_get_pid(engine->conn)); | |
499 | return errAuthorizationInternal; | |
500 | } | |
501 | } | |
502 | } | |
503 | ||
504 | static OSStatus | |
505 | _evaluate_authentication(engine_t engine, rule_t rule) | |
506 | { | |
507 | OSStatus status = errAuthorizationDenied; | |
508 | ccaudit_t ccaudit = ccaudit_create(engine->proc, engine->auth, AUE_ssauthint); | |
509 | LOGV("engine[%i]: evaluate authentication", connection_get_pid(engine->conn)); | |
510 | _set_rule_hints(engine->hints, rule); | |
511 | _set_session_hints(engine, rule); | |
512 | ||
513 | CFArrayRef mechanisms = rule_get_mechanisms(rule); | |
514 | if (!(CFArrayGetCount(mechanisms) > 0)) { | |
515 | mechanisms = rule_get_mechanisms(engine->authenticateRule); | |
516 | } | |
517 | require_action(CFArrayGetCount(mechanisms) > 0, done, LOGV("engine[%i]: error no mechanisms found", connection_get_pid(engine->conn))); | |
518 | ||
519 | int64_t ruleTries = rule_get_tries(rule); | |
520 | for (engine->tries = 0; engine->tries < ruleTries; engine->tries++) { | |
521 | ||
522 | auth_items_set_data(engine->hints, AGENT_HINT_RETRY_REASON, &engine->reason, sizeof(engine->reason)); | |
523 | auth_items_set_int(engine->hints, AGENT_HINT_TRIES, engine->tries); | |
524 | ||
525 | status = _evaluate_mechanisms(engine, mechanisms); | |
526 | ||
527 | LOGV("engine[%i]: evaluate mechanisms result %i", connection_get_pid(engine->conn), status); | |
528 | ||
529 | // successfully ran mechanisms to obtain credential | |
530 | if (status == errAuthorizationSuccess) { | |
531 | // deny is the default | |
532 | status = errAuthorizationDenied; | |
533 | ||
534 | credential_t newCred = NULL; | |
535 | if (auth_items_exist(engine->context, "uid")) { | |
536 | newCred = credential_create(auth_items_get_uint(engine->context, "uid")); | |
537 | } else { | |
538 | LOGV("engine[%i]: mechanism failed to return a valid uid", connection_get_pid(engine->conn)); | |
539 | } | |
540 | ||
541 | if (newCred) { | |
542 | if (credential_get_valid(newCred)) { | |
543 | LOG("UID %u authenticated as user %s (UID %u) for right '%s'", auth_token_get_uid(engine->auth), credential_get_name(newCred), credential_get_uid(newCred), engine->currentRightName); | |
544 | ccaudit_log_success(ccaudit, newCred, engine->currentRightName); | |
545 | } else { | |
546 | LOG("UID %u failed to authenticate as user '%s' for right '%s'", auth_token_get_uid(engine->auth), auth_items_get_string(engine->context, "username"), engine->currentRightName); | |
547 | ccaudit_log_failure(ccaudit, auth_items_get_string(engine->context, "username"), engine->currentRightName); | |
548 | } | |
549 | ||
550 | status = _evaluate_user_credential_for_rule(engine, newCred, rule, true, false, &engine->reason); | |
551 | ||
552 | if (status == errAuthorizationSuccess) { | |
553 | _engine_set_credential(engine, newCred, rule_get_shared(rule)); | |
554 | CFReleaseSafe(newCred); | |
555 | ||
556 | if (auth_token_least_privileged(engine->auth)) { | |
557 | credential_t rightCred = credential_create_with_right(engine->currentRightName); | |
558 | _engine_set_credential(engine, rightCred, rule_get_shared(rule)); | |
559 | CFReleaseSafe(rightCred); | |
560 | } | |
561 | ||
562 | session_t session = auth_token_get_session(engine->auth); | |
563 | if (credential_get_uid(newCred) == session_get_uid(session)) { | |
564 | LOGV("engine[%i]: authenticated as the session owner", connection_get_pid(engine->conn)); | |
565 | session_set_attributes(auth_token_get_session(engine->auth), AU_SESSION_FLAG_HAS_AUTHENTICATED); | |
566 | } | |
567 | ||
568 | break; | |
569 | } | |
570 | ||
571 | CFReleaseSafe(newCred); | |
572 | } | |
573 | ||
574 | } else if (status == errAuthorizationCanceled || status == errAuthorizationInternal) { | |
575 | break; | |
576 | } else if (status == errAuthorizationDenied) { | |
577 | engine->reason = invalidPassphrase; | |
578 | } | |
579 | } | |
580 | ||
581 | if (engine->tries == ruleTries) { | |
582 | engine->reason = tooManyTries; | |
583 | auth_items_set_data(engine->hints, AGENT_HINT_RETRY_REASON, &engine->reason, sizeof(engine->reason)); | |
584 | auth_items_set_int(engine->hints, AGENT_HINT_TRIES, engine->tries); | |
585 | _evaluate_mechanisms(engine, mechanisms); | |
586 | ccaudit_log(ccaudit, engine->currentRightName, NULL, 1113); | |
587 | } | |
588 | ||
589 | done: | |
590 | CFReleaseSafe(ccaudit); | |
591 | ||
592 | return status; | |
593 | } | |
594 | ||
595 | static bool | |
596 | _check_entitlement_for_rule(engine_t engine, rule_t rule) | |
597 | { | |
598 | bool entitled = false; | |
599 | CFTypeRef value = NULL; | |
600 | ||
601 | if (rule_check_flags(rule, RuleFlagEntitledAndGroup)) { | |
602 | if (auth_token_has_entitlement_for_right(engine->auth, engine->currentRightName)) { | |
603 | if (credential_check_membership(auth_token_get_credential(engine->auth), rule_get_group(rule))) { | |
604 | LOGV("engine[%i]: creator of authorization has entitlement for right %s and is member of group '%s'", connection_get_pid(engine->conn), engine->currentRightName, rule_get_group(rule)); | |
605 | entitled = true; | |
606 | goto done; | |
607 | } | |
608 | } | |
609 | } | |
610 | ||
611 | if (rule_check_flags(rule, RuleFlagVPNEntitledAndGroup)) { | |
612 | // com.apple.networking.vpn.configuration is an array we only check for it's existence | |
613 | value = auth_token_copy_entitlement_value(engine->auth, "com.apple.networking.vpn.configuration"); | |
614 | if (value) { | |
615 | if (credential_check_membership(auth_token_get_credential(engine->auth), rule_get_group(rule))) { | |
616 | LOGV("engine[%i]: creator of authorization has VPN entitlement and is member of group '%s'", connection_get_pid(engine->conn), rule_get_group(rule)); | |
617 | entitled = true; | |
618 | goto done; | |
619 | } | |
620 | } | |
621 | } | |
622 | ||
623 | done: | |
624 | CFReleaseSafe(value); | |
625 | return entitled; | |
626 | } | |
627 | ||
628 | static OSStatus | |
629 | _evaluate_class_user(engine_t engine, rule_t rule) | |
630 | { | |
631 | __block OSStatus status = errAuthorizationDenied; | |
632 | ||
633 | if (_check_entitlement_for_rule(engine,rule)) { | |
634 | return errAuthorizationSuccess; | |
635 | } | |
636 | ||
637 | if (rule_get_allow_root(rule) && auth_token_get_uid(engine->auth) == 0) { | |
638 | LOGV("engine[%i]: creator of authorization has uid == 0 granting right %s", connection_get_pid(engine->conn), engine->currentRightName); | |
639 | return errAuthorizationSuccess; | |
640 | } | |
641 | ||
642 | if (!rule_get_authenticate_user(rule)) { | |
643 | status = _evaluate_user_credential_for_rule(engine, engine->sessionCredential, rule, true, true, NULL); | |
644 | ||
645 | if (status == errAuthorizationSuccess) { | |
646 | return errAuthorizationSuccess; | |
647 | } | |
648 | ||
649 | return errAuthorizationDenied; | |
650 | } | |
651 | ||
652 | // First -- check all the credentials we have either acquired or currently have | |
653 | _cf_set_iterate(engine->credentials, ^bool(CFTypeRef value) { | |
654 | credential_t cred = (credential_t)value; | |
655 | // Passed-in user credentials are allowed for least-privileged mode | |
656 | if (auth_token_least_privileged(engine->auth) && !credential_is_right(cred) && credential_get_valid(cred)) { | |
657 | status = _evaluate_user_credential_for_rule(engine, cred, rule, false, false, NULL); | |
658 | if (errAuthorizationSuccess == status) { | |
659 | credential_t rightCred = credential_create_with_right(engine->currentRightName); | |
660 | _engine_set_credential(engine,rightCred,rule_get_shared(rule)); | |
661 | CFReleaseSafe(rightCred); | |
662 | return false; // exit loop | |
663 | } | |
664 | } | |
665 | ||
666 | status = _evaluate_credential_for_rule(engine, cred, rule, false, false, NULL); | |
667 | if (status == errAuthorizationSuccess) { | |
668 | return false; // exit loop | |
669 | } | |
670 | return true; | |
671 | }); | |
672 | ||
673 | if (status == errAuthorizationSuccess) { | |
674 | return status; | |
675 | } | |
676 | ||
677 | // Second -- go through the credentials associated to the authorization token session/auth token | |
678 | _cf_set_iterate(engine->effectiveCredentials, ^bool(CFTypeRef value) { | |
679 | credential_t cred = (credential_t)value; | |
680 | status = _evaluate_credential_for_rule(engine, cred, rule, false, false, NULL); | |
681 | if (status == errAuthorizationSuccess) { | |
682 | // Add the credential we used to the output set. | |
683 | _engine_set_credential(engine, cred, false); | |
684 | return false; // exit loop | |
685 | } | |
686 | return true; | |
687 | }); | |
688 | ||
689 | if (status == errAuthorizationSuccess) { | |
690 | return status; | |
691 | } | |
692 | ||
693 | // Finally - we didn't find a credential. Obtain a new credential if our flags let us do so. | |
694 | if (!(engine->flags & kAuthorizationFlagExtendRights)) { | |
695 | LOGV("engine[%i]: authorization denied (kAuthorizationFlagExtendRights not set)", connection_get_pid(engine->conn)); | |
696 | return errAuthorizationDenied; | |
697 | } | |
698 | ||
699 | // authorization that timeout immediately cannot be preauthorized | |
700 | if (engine->flags & kAuthorizationFlagPreAuthorize && rule_get_timeout(rule) == 0) { | |
701 | return errAuthorizationSuccess; | |
702 | } | |
703 | ||
704 | if (!(engine->flags & kAuthorizationFlagInteractionAllowed)) { | |
705 | LOGV("engine[%i]: Interaction not allowed (kAuthorizationFlagInteractionAllowed not set)", connection_get_pid(engine->conn)); | |
706 | return errAuthorizationInteractionNotAllowed; | |
707 | } | |
708 | ||
709 | if (!(session_get_attributes(auth_token_get_session(engine->auth)) & AU_SESSION_FLAG_HAS_GRAPHIC_ACCESS)) { | |
710 | LOGV("engine[%i]: Interaction not allowed (session has no ui access)", connection_get_pid(engine->conn)); | |
711 | return errAuthorizationInteractionNotAllowed; | |
712 | } | |
713 | ||
714 | if (server_in_dark_wake()) { | |
715 | LOGV("engine[%i]: authorization denied (in DarkWake)", connection_get_pid(engine->conn)); | |
716 | return errAuthorizationDenied; | |
717 | } | |
718 | ||
719 | return _evaluate_authentication(engine,rule); | |
720 | } | |
721 | ||
722 | static OSStatus | |
723 | _evaluate_class_rule(engine_t engine, rule_t rule) | |
724 | { | |
725 | __block OSStatus status = errAuthorizationDenied; | |
726 | int64_t kofn = rule_get_kofn(rule); | |
727 | ||
728 | uint32_t total = (uint32_t)rule_get_delegates_count(rule); | |
729 | __block uint32_t success_count = 0; | |
730 | __block uint32_t count = 0; | |
731 | LOGV("engine[%i]: ** rule %s has %zi delegates kofn = %lli", connection_get_pid(engine->conn), rule_get_name(rule), total, kofn); | |
732 | rule_delegates_iterator(rule, ^bool(rule_t delegate) { | |
733 | count++; | |
734 | ||
735 | if (kofn != 0 && success_count == kofn) { | |
736 | status = errAuthorizationSuccess; | |
737 | return false; | |
738 | } | |
739 | ||
740 | LOGV("engine[%i]: * evaluate rule %s (%i)", connection_get_pid(engine->conn), rule_get_name(delegate), count); | |
741 | status = _evaluate_rule(engine, delegate); | |
742 | ||
743 | // if status is cancel/internal error abort | |
744 | if ((status == errAuthorizationCanceled) || (status == errAuthorizationInternal)) | |
745 | return false; | |
746 | ||
747 | if (status != errAuthorizationSuccess) { | |
748 | if (kofn != 0) { | |
749 | // if remaining is less then required abort | |
750 | if ((total - count) < (kofn - success_count)) { | |
751 | LOGD("engine[%i]: rule evaluation remaining: %i, required: %lli", connection_get_pid(engine->conn), (total - count), (kofn - success_count)); | |
752 | return false; | |
753 | } | |
754 | return true; | |
755 | } | |
756 | return false; | |
757 | } else { | |
758 | success_count++; | |
759 | return true; | |
760 | } | |
761 | }); | |
762 | ||
763 | return status; | |
764 | } | |
765 | ||
766 | static OSStatus | |
767 | _evaluate_class_mechanism(engine_t engine, rule_t rule) | |
768 | { | |
769 | OSStatus status = errAuthorizationDenied; | |
770 | CFArrayRef mechanisms = NULL; | |
771 | ||
772 | require_action(rule_get_mechanisms_count(rule) > 0, done, status = errAuthorizationSuccess; LOGV("engine[%i]: no mechanisms specified", connection_get_pid(engine->conn))); | |
773 | ||
774 | mechanisms = rule_get_mechanisms(rule); | |
775 | ||
776 | if (server_in_dark_wake()) { | |
777 | CFIndex count = CFArrayGetCount(mechanisms); | |
778 | for (CFIndex i = 0; i < count; i++) { | |
779 | if (!mechanism_is_privileged((mechanism_t)CFArrayGetValueAtIndex(mechanisms, i))) { | |
780 | LOGV("engine[%i]: authorization denied (in DarkWake)", connection_get_pid(engine->conn)); | |
781 | goto done; | |
782 | } | |
783 | } | |
784 | } | |
785 | ||
786 | int64_t ruleTries = rule_get_tries(rule); | |
787 | engine->tries = 0; | |
788 | do { | |
789 | auth_items_set_data(engine->hints, AGENT_HINT_RETRY_REASON, &engine->reason, sizeof(engine->reason)); | |
790 | auth_items_set_int(engine->hints, AGENT_HINT_TRIES, engine->tries); | |
791 | ||
792 | status = _evaluate_mechanisms(engine, mechanisms); | |
793 | LOGV("engine[%i]: evaluate mechanisms result %i", connection_get_pid(engine->conn), status); | |
794 | ||
795 | if ((status == errAuthorizationSuccess) || (status == errAuthorizationCanceled)) { | |
796 | ||
797 | if (status == errAuthorizationSuccess) { | |
798 | credential_t newCred = NULL; | |
799 | if (auth_items_exist(engine->context, "uid")) { | |
800 | newCred = credential_create(auth_items_get_uint(engine->context, "uid")); | |
801 | } else { | |
802 | LOGV("engine[%i]: mechanism did not return a uid", connection_get_pid(engine->conn)); | |
803 | } | |
804 | ||
805 | if (newCred) { | |
806 | _engine_set_credential(engine, newCred, rule_get_shared(rule)); | |
807 | ||
808 | if (auth_token_least_privileged(engine->auth)) { | |
809 | credential_t rightCred = credential_create_with_right(engine->currentRightName); | |
810 | _engine_set_credential(engine, rightCred, rule_get_shared(rule)); | |
811 | CFReleaseSafe(rightCred); | |
812 | } | |
813 | ||
814 | if (strcmp(engine->currentRightName, "system.login.console") == 0 && !auth_items_exist(engine->context, AGENT_CONTEXT_AUTO_LOGIN)) { | |
815 | session_set_attributes(auth_token_get_session(engine->auth), AU_SESSION_FLAG_HAS_AUTHENTICATED); | |
816 | } | |
817 | ||
818 | CFReleaseSafe(newCred); | |
819 | } | |
820 | } | |
821 | } // mechanism errAuthorizationSuccess | |
822 | ||
823 | engine->tries++; | |
824 | ||
825 | } while ( (status == errAuthorizationDenied) // only if we have an expected faulure we continue | |
826 | && ((ruleTries == 0) || ((ruleTries > 0) && engine->tries < ruleTries))); // ruleTries == 0 means we try forever | |
827 | // ruleTires > 0 means we try upto ruleTries times | |
828 | done: | |
829 | return status; | |
830 | } | |
831 | ||
832 | static OSStatus | |
833 | _evaluate_rule(engine_t engine, rule_t rule) | |
834 | { | |
835 | if (rule_check_flags(rule, RuleFlagEntitled)) { | |
836 | if (auth_token_has_entitlement_for_right(engine->auth, engine->currentRightName)) { | |
837 | LOGV("engine[%i]: rule allow, creator of authorization has entitlement for right %s", connection_get_pid(engine->conn), engine->currentRightName); | |
838 | return errAuthorizationSuccess; | |
839 | } | |
840 | } | |
841 | ||
842 | if (rule_check_flags(rule, RuleFlagRequireAppleSigned)) { | |
843 | if (!auth_token_apple_signed(engine->auth)) { | |
844 | LOGV("engine[%i]: rule deny, creator of authorization is not signed by apple", connection_get_pid(engine->conn)); | |
845 | return errAuthorizationDenied; | |
846 | } | |
847 | } | |
848 | ||
849 | switch (rule_get_class(rule)) { | |
850 | case RC_ALLOW: | |
851 | LOGV("engine[%i]: rule set to allow", connection_get_pid(engine->conn)); | |
852 | return errAuthorizationSuccess; | |
853 | case RC_DENY: | |
854 | LOGV("engine[%i]: rule set to deny", connection_get_pid(engine->conn)); | |
855 | return errAuthorizationDenied; | |
856 | case RC_USER: | |
857 | return _evaluate_class_user(engine,rule); | |
858 | case RC_RULE: | |
859 | return _evaluate_class_rule(engine, rule); | |
860 | case RC_MECHANISM: | |
861 | return _evaluate_class_mechanism(engine, rule); | |
862 | default: | |
863 | LOGV("engine[%i]: invalid class for rule or rule not found", connection_get_pid(engine->conn)); | |
864 | return errAuthorizationInternal; | |
865 | } | |
866 | } | |
867 | ||
868 | static rule_t | |
869 | _find_rule(engine_t engine, authdb_connection_t dbconn, const char * string) | |
870 | { | |
871 | rule_t r = NULL; | |
872 | size_t sLen = strlen(string); | |
873 | ||
874 | char * buf = calloc(1u, sLen + 1); | |
875 | strlcpy(buf, string, sLen + 1); | |
876 | char * ptr = buf + sLen; | |
877 | __block int64_t count = 0; | |
878 | ||
879 | for (;;) { | |
880 | ||
881 | // lookup rule | |
882 | authdb_step(dbconn, "SELECT COUNT(name) AS cnt FROM rules WHERE name = ? AND type = 1", | |
883 | ^(sqlite3_stmt *stmt) { | |
884 | sqlite3_bind_text(stmt, 1, buf, -1, NULL); | |
885 | }, ^bool(auth_items_t data) { | |
886 | count = auth_items_get_int64(data, "cnt"); | |
887 | return false; | |
888 | }); | |
889 | ||
890 | if (count > 0) { | |
891 | r = rule_create_with_string(buf, dbconn); | |
892 | goto done; | |
893 | } | |
894 | ||
895 | // if buf ends with a . and we didn't find a rule remove . | |
896 | if (*ptr == '.') { | |
897 | *ptr = '\0'; | |
898 | } | |
899 | // find any remaining . and truncate the string | |
900 | ptr = strrchr(buf, '.'); | |
901 | if (ptr) { | |
902 | *(ptr+1) = '\0'; | |
903 | } else { | |
904 | break; | |
905 | } | |
906 | } | |
907 | ||
908 | done: | |
909 | free_safe(buf); | |
910 | ||
911 | // set default if we didn't find a rule | |
912 | if (r == NULL) { | |
913 | r = rule_create_with_string("", dbconn); | |
914 | if (rule_get_id(r) == 0) { | |
915 | CFReleaseNull(r); | |
916 | LOGE("engine[%i]: default rule lookup error (missing), using builtin defaults", connection_get_pid(engine->conn)); | |
917 | r = rule_create_default(); | |
918 | } | |
919 | } | |
920 | return r; | |
921 | } | |
922 | ||
923 | static void _parse_enviroment(engine_t engine, auth_items_t enviroment) | |
924 | { | |
925 | require(enviroment != NULL, done); | |
926 | ||
927 | #if DEBUG | |
928 | LOGV("engine[%i]: Dumping Enviroment", connection_get_pid(engine->conn)); | |
929 | _show_cf(enviroment); | |
930 | #endif | |
931 | ||
932 | // Check if a credential was passed into the environment and we were asked to extend the rights | |
933 | if (engine->flags & kAuthorizationFlagExtendRights) { | |
934 | const char * user = auth_items_get_string(enviroment, kAuthorizationEnvironmentUsername); | |
935 | const char * pass = auth_items_get_string(enviroment, kAuthorizationEnvironmentPassword); | |
936 | bool shared = auth_items_exist(enviroment, kAuthorizationEnvironmentShared); | |
937 | require(user != NULL, done); | |
938 | ||
939 | struct passwd *pw = getpwnam(user); | |
940 | require_action(pw != NULL, done, LOGE("engine[%i]: user not found %s", connection_get_pid(engine->conn), user)); | |
941 | ||
942 | int checkpw_status = checkpw_internal(pw, pass ? pass : ""); | |
943 | require_action(checkpw_status == CHECKPW_SUCCESS, done, LOGE("engine[%i]: checkpw() returned %d; failed to authenticate user %s (uid %u).", connection_get_pid(engine->conn), checkpw_status, pw->pw_name, pw->pw_uid)); | |
944 | ||
945 | credential_t cred = credential_create(pw->pw_uid); | |
946 | if (credential_get_valid(cred)) { | |
947 | LOG("engine[%i]: checkpw() succeeded, creating credential for user %s", connection_get_pid(engine->conn), user); | |
948 | _engine_set_credential(engine, cred, shared); | |
949 | ||
950 | auth_items_set_string(engine->context, kAuthorizationEnvironmentUsername, user); | |
951 | auth_items_set_string(engine->context, kAuthorizationEnvironmentPassword, pass ? pass : ""); | |
952 | } | |
953 | CFReleaseSafe(cred); | |
954 | } | |
955 | ||
956 | done: | |
957 | endpwent(); | |
958 | return; | |
959 | } | |
960 | ||
961 | static bool _verify_sandbox(engine_t engine, const char * right) | |
962 | { | |
963 | pid_t pid = process_get_pid(engine->proc); | |
964 | if (sandbox_check(pid, "authorization-right-obtain", SANDBOX_FILTER_RIGHT_NAME, right)) { | |
965 | LOGE("Sandbox denied authorizing right '%s' by client '%s' [%d]", right, process_get_code_url(engine->proc), pid); | |
966 | return false; | |
967 | } | |
968 | ||
969 | pid = auth_token_get_pid(engine->auth); | |
970 | if (auth_token_get_sandboxed(engine->auth) && sandbox_check(pid, "authorization-right-obtain", SANDBOX_FILTER_RIGHT_NAME, right)) { | |
971 | LOGE("Sandbox denied authorizing right '%s' for authorization created by '%s' [%d]", right, auth_token_get_code_url(engine->auth), pid); | |
972 | return false; | |
973 | } | |
974 | ||
975 | return true; | |
976 | } | |
977 | ||
978 | #pragma mark - | |
979 | #pragma mark engine methods | |
980 | ||
981 | OSStatus engine_authorize(engine_t engine, auth_rights_t rights, auth_items_t enviroment, AuthorizationFlags flags) | |
982 | { | |
983 | __block OSStatus status = errAuthorizationSuccess; | |
984 | __block bool savePassword = false; | |
985 | ccaudit_t ccaudit = NULL; | |
986 | ||
987 | require(rights != NULL, done); | |
988 | ||
989 | ccaudit = ccaudit_create(engine->proc, engine->auth, AUE_ssauthorize); | |
990 | if (auth_rights_get_count(rights) > 0) { | |
991 | ccaudit_log(ccaudit, "begin evaluation", NULL, 0); | |
992 | } | |
993 | ||
994 | engine->flags = flags; | |
995 | ||
996 | if (enviroment) { | |
997 | _parse_enviroment(engine, enviroment); | |
998 | auth_items_copy(engine->hints, enviroment); | |
999 | } | |
1000 | ||
1001 | auth_items_copy(engine->context, auth_token_get_context(engine->auth)); | |
1002 | ||
1003 | engine->dismissed = false; | |
1004 | auth_rights_clear(engine->grantedRights); | |
1005 | ||
1006 | auth_rights_iterate(rights, ^bool(const char *key) { | |
1007 | if (!key) | |
1008 | return true; | |
1009 | ||
1010 | ||
1011 | if (!_verify_sandbox(engine, key)) { | |
1012 | status = errAuthorizationDenied; | |
1013 | return false; | |
1014 | } | |
1015 | ||
1016 | authdb_connection_t dbconn = authdb_connection_acquire(server_get_database()); // get db handle | |
1017 | ||
1018 | LOGV("engine[%i]: evaluate right %s", connection_get_pid(engine->conn), key); | |
1019 | rule_t rule = _find_rule(engine, dbconn, key); | |
1020 | const char * rule_name = rule_get_name(rule); | |
1021 | if (rule_name && (strcasecmp(rule_name, "") == 0)) { | |
1022 | rule_name = "default (not defined)"; | |
1023 | } | |
1024 | LOGV("engine[%i]: using rule %s", connection_get_pid(engine->conn), rule_name); | |
1025 | ||
1026 | // only need the hints & mechanisms if we are going to show ui | |
1027 | if (engine->flags & kAuthorizationFlagInteractionAllowed) { | |
1028 | _set_right_hints(engine->hints, key); | |
1029 | _set_localization_hints(dbconn, engine->hints, rule); | |
1030 | if (!engine->authenticateRule) { | |
1031 | engine->authenticateRule = rule_create_with_string("authenticate", dbconn); | |
1032 | } | |
1033 | } | |
1034 | ||
1035 | authdb_connection_release(&dbconn); // release db handle | |
1036 | ||
1037 | engine->currentRightName = key; | |
1038 | engine->currentRule = rule; | |
1039 | ||
1040 | ccaudit_log(ccaudit, key, rule_name, 0); | |
1041 | ||
1042 | status = _evaluate_rule(engine, engine->currentRule); | |
1043 | switch (status) { | |
1044 | case errAuthorizationSuccess: | |
1045 | auth_rights_add(engine->grantedRights, key); | |
1046 | auth_rights_set_flags(engine->grantedRights, key, auth_rights_get_flags(rights,key)); | |
1047 | ||
1048 | if ((engine->flags & kAuthorizationFlagPreAuthorize) && | |
1049 | (rule_get_class(engine->currentRule) == RC_USER) && | |
1050 | (rule_get_timeout(engine->currentRule) == 0)) { | |
1051 | auth_rights_set_flags(engine->grantedRights, engine->currentRightName, kAuthorizationFlagPreAuthorize); | |
1052 | } | |
1053 | ||
1054 | LOG("Succeeded authorizing right '%s' by client '%s' [%d] for authorization created by '%s' [%d] (%X,%d)", | |
1055 | key, process_get_code_url(engine->proc), process_get_pid(engine->proc), | |
1056 | auth_token_get_code_url(engine->auth), auth_token_get_pid(engine->auth), engine->flags, auth_token_least_privileged(engine->auth)); | |
1057 | if (rule_get_extract_password(engine->currentRule)) { | |
1058 | savePassword = true; | |
1059 | } | |
1060 | break; | |
1061 | case errAuthorizationDenied: | |
1062 | case errAuthorizationInteractionNotAllowed: | |
1063 | case errAuthorizationCanceled: | |
1064 | if (engine->flags & kAuthorizationFlagInteractionAllowed) { | |
1065 | LOG("Failed to authorize right '%s' by client '%s' [%d] for authorization created by '%s' [%d] (%X,%d) (%i)", | |
1066 | key, process_get_code_url(engine->proc), process_get_pid(engine->proc), | |
1067 | auth_token_get_code_url(engine->auth), auth_token_get_pid(engine->auth), engine->flags, auth_token_least_privileged(engine->auth), status); | |
1068 | } else { | |
1069 | LOGV("Failed to authorize right '%s' by client '%s' [%d] for authorization created by '%s' [%d] (%X,%d) (%i)", | |
1070 | key, process_get_code_url(engine->proc), process_get_pid(engine->proc), | |
1071 | auth_token_get_code_url(engine->auth), auth_token_get_pid(engine->auth), engine->flags, auth_token_least_privileged(engine->auth), status); | |
1072 | } | |
1073 | break; | |
1074 | default: | |
1075 | LOGE("engine[%i]: evaluate returned %i returning errAuthorizationInternal", connection_get_pid(engine->conn), status); | |
1076 | status = errAuthorizationInternal; | |
1077 | break; | |
1078 | } | |
1079 | ||
1080 | ccaudit_log_authorization(ccaudit, engine->currentRightName, status); | |
1081 | ||
1082 | CFReleaseSafe(rule); | |
1083 | engine->currentRightName = NULL; | |
1084 | engine->currentRule = NULL; | |
1085 | ||
1086 | auth_items_remove_with_flags(engine->hints, kEngineHintsFlagTemporary); | |
1087 | ||
1088 | if (!(engine->flags & kAuthorizationFlagPartialRights) && (status != errAuthorizationSuccess)) { | |
1089 | return false; | |
1090 | } | |
1091 | ||
1092 | return true; | |
1093 | }); | |
1094 | ||
1095 | if ((engine->flags & kAuthorizationFlagPartialRights) && (auth_rights_get_count(engine->grantedRights) > 0)) { | |
1096 | status = errAuthorizationSuccess; | |
1097 | } | |
1098 | ||
1099 | if (engine->dismissed) { | |
1100 | status = errAuthorizationDenied; | |
1101 | } | |
1102 | ||
1103 | LOGV("engine[%i]: authorize result: %i", connection_get_pid(engine->conn), status); | |
1104 | ||
1105 | if ((engine->flags & kAuthorizationFlagExtendRights) && !(engine->flags & kAuthorizationFlagDestroyRights)) { | |
1106 | _cf_set_iterate(engine->credentials, ^bool(CFTypeRef value) { | |
1107 | credential_t cred = (credential_t)value; | |
1108 | // skip all uid credentials when running in least privileged | |
1109 | if (auth_token_least_privileged(engine->auth) && !credential_is_right(cred)) | |
1110 | return true; | |
1111 | ||
1112 | session_t session = auth_token_get_session(engine->auth); | |
1113 | auth_token_set_credential(engine->auth, cred); | |
1114 | if (credential_get_shared(cred)) { | |
1115 | session_set_credential(session, cred); | |
1116 | } | |
1117 | if (credential_is_right(cred)) { | |
1118 | LOGV("engine[%i]: adding least privileged %scredential %s to authorization", connection_get_pid(engine->conn), credential_get_shared(cred) ? "shared " : "", credential_get_name(cred)); | |
1119 | } else { | |
1120 | LOGV("engine[%i]: adding %scredential %s (%i) to authorization", connection_get_pid(engine->conn), credential_get_shared(cred) ? "shared " : "", credential_get_name(cred), credential_get_uid(cred)); | |
1121 | } | |
1122 | return true; | |
1123 | }); | |
1124 | } | |
1125 | ||
1126 | if ((status == errAuthorizationSuccess) || (status == errAuthorizationCanceled)) { | |
1127 | if (savePassword && (status == errAuthorizationSuccess)) { | |
1128 | auth_items_set_flags(engine->context, kAuthorizationEnvironmentPassword, kAuthorizationContextFlagExtractable); | |
1129 | } | |
1130 | ||
1131 | auth_items_copy_with_flags(auth_token_get_context(engine->auth), engine->context, kAuthorizationContextFlagExtractable); | |
1132 | } | |
1133 | ||
1134 | if (auth_rights_get_count(rights) > 0) { | |
1135 | ccaudit_log(ccaudit, "end evaluation", NULL, status); | |
1136 | } | |
1137 | ||
1138 | #if DEBUG | |
1139 | LOGV("engine[%i]: ********** Dumping auth->credentials **********", connection_get_pid(engine->conn)); | |
1140 | auth_token_credentials_iterate(engine->auth, ^bool(credential_t cred) { | |
1141 | _show_cf(cred); | |
1142 | return true; | |
1143 | }); | |
1144 | LOGV("engine[%i]: ********** Dumping session->credentials **********", connection_get_pid(engine->conn)); | |
1145 | session_credentials_iterate(auth_token_get_session(engine->auth), ^bool(credential_t cred) { | |
1146 | _show_cf(cred); | |
1147 | return true; | |
1148 | }); | |
1149 | LOGV("engine[%i]: ********** Dumping engine->context **********", connection_get_pid(engine->conn)); | |
1150 | _show_cf(engine->context); | |
1151 | LOGV("engine[%i]: ********** Dumping auth->context **********", connection_get_pid(engine->conn)); | |
1152 | _show_cf(auth_token_get_context(engine->auth)); | |
1153 | LOGV("engine[%i]: ********** Dumping granted rights **********", connection_get_pid(engine->conn)); | |
1154 | _show_cf(engine->grantedRights); | |
1155 | #endif | |
1156 | ||
1157 | done: | |
1158 | auth_items_clear(engine->context); | |
1159 | auth_items_clear(engine->sticky_context); | |
1160 | CFReleaseSafe(ccaudit); | |
1161 | CFDictionaryRemoveAllValues(engine->mechanism_agents); | |
1162 | ||
1163 | return status; | |
1164 | } | |
1165 | ||
1166 | static bool | |
1167 | _wildcard_right_exists(engine_t engine, const char * right) | |
1168 | { | |
1169 | // checks if a wild card right exists | |
1170 | // ex: com.apple. system. | |
1171 | bool exists = false; | |
1172 | rule_t rule = NULL; | |
1173 | authdb_connection_t dbconn = authdb_connection_acquire(server_get_database()); // get db handle | |
1174 | require(dbconn != NULL, done); | |
1175 | ||
1176 | rule = _find_rule(engine, dbconn, right); | |
1177 | require(rule != NULL, done); | |
1178 | ||
1179 | const char * ruleName = rule_get_name(rule); | |
1180 | require(ruleName != NULL, done); | |
1181 | size_t len = strlen(ruleName); | |
1182 | require(len != 0, done); | |
1183 | ||
1184 | if (ruleName[len-1] == '.') { | |
1185 | exists = true; | |
1186 | goto done; | |
1187 | } | |
1188 | ||
1189 | done: | |
1190 | authdb_connection_release(&dbconn); | |
1191 | CFReleaseSafe(rule); | |
1192 | ||
1193 | return exists; | |
1194 | } | |
1195 | ||
1196 | // Validate db right modification | |
1197 | ||
1198 | // meta rights are constructed as follows: | |
1199 | // we don't allow setting of wildcard rights, so you can only be more specific | |
1200 | // note that you should never restrict things with a wildcard right without disallowing | |
1201 | // changes to the entire domain. ie. | |
1202 | // system.privilege. -> never | |
1203 | // config.add.system.privilege. -> never | |
1204 | // config.modify.system.privilege. -> never | |
1205 | // config.delete.system.privilege. -> never | |
1206 | // For now we don't allow any configuration of configuration rules | |
1207 | // config.config. -> never | |
1208 | ||
1209 | OSStatus engine_verify_modification(engine_t engine, rule_t rule, bool remove, bool force_modify) | |
1210 | { | |
1211 | OSStatus status = errAuthorizationDenied; | |
1212 | auth_rights_t checkRight = NULL; | |
1213 | char buf[BUFSIZ]; | |
1214 | memset(buf, 0, sizeof(buf)); | |
1215 | ||
1216 | const char * right = rule_get_name(rule); | |
1217 | require(right != NULL, done); | |
1218 | size_t len = strlen(right); | |
1219 | require(len != 0, done); | |
1220 | ||
1221 | require_action(right[len-1] != '.', done, LOGE("engine[%i]: not allowed to set wild card rules", connection_get_pid(engine->conn))); | |
1222 | ||
1223 | if (strncasecmp(right, kConfigRight, strlen(kConfigRight)) == 0) { | |
1224 | // special handling of meta right change: | |
1225 | // config.add. config.modify. config.remove. config.{}. | |
1226 | // check for config.<right> (which always starts with config.config.) | |
1227 | strlcat(buf, kConfigRight, sizeof(buf)); | |
1228 | } else { | |
1229 | bool existing = (rule_get_id(rule) != 0) ? true : _wildcard_right_exists(engine, right); | |
1230 | if (!remove) { | |
1231 | if (existing || force_modify) { | |
1232 | strlcat(buf, kAuthorizationConfigRightModify,sizeof(buf)); | |
1233 | } else { | |
1234 | strlcat(buf, kAuthorizationConfigRightAdd, sizeof(buf)); | |
1235 | } | |
1236 | } else { | |
1237 | if (existing) { | |
1238 | strlcat(buf, kAuthorizationConfigRightRemove, sizeof(buf)); | |
1239 | } else { | |
1240 | status = errAuthorizationSuccess; | |
1241 | goto done; | |
1242 | } | |
1243 | } | |
1244 | } | |
1245 | ||
1246 | strlcat(buf, right, sizeof(buf)); | |
1247 | ||
1248 | checkRight = auth_rights_create(); | |
1249 | auth_rights_add(checkRight, buf); | |
1250 | status = engine_authorize(engine, checkRight, NULL, kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights); | |
1251 | ||
1252 | done: | |
1253 | LOGV("engine[%i]: authorizing %s for db modification: %i", connection_get_pid(engine->conn), right, status); | |
1254 | CFReleaseSafe(checkRight); | |
1255 | return status; | |
1256 | } | |
1257 | ||
1258 | void | |
1259 | _engine_set_credential(engine_t engine, credential_t cred, bool shared) | |
1260 | { | |
1261 | LOGV("engine[%i]: adding %scredential %s (%i) to engine shared: %i", connection_get_pid(engine->conn), credential_get_shared(cred) ? "shared " : "", credential_get_name(cred), credential_get_uid(cred), shared); | |
1262 | CFSetSetValue(engine->credentials, cred); | |
1263 | if (shared) { | |
1264 | credential_t sharedCred = credential_create_with_credential(cred, true); | |
1265 | CFSetSetValue(engine->credentials, sharedCred); | |
1266 | CFReleaseSafe(sharedCred); | |
1267 | } | |
1268 | } | |
1269 | ||
1270 | auth_rights_t | |
1271 | engine_get_granted_rights(engine_t engine) | |
1272 | { | |
1273 | return engine->grantedRights; | |
1274 | } | |
1275 | ||
1276 | CFAbsoluteTime engine_get_time(engine_t engine) | |
1277 | { | |
1278 | return engine->now; | |
1279 | } | |
1280 | ||
1281 | void engine_destroy_agents(engine_t engine) | |
1282 | { | |
1283 | engine->dismissed = true; | |
1284 | ||
1285 | _cf_dictionary_iterate(engine->mechanism_agents, ^bool(CFTypeRef key __attribute__((__unused__)), CFTypeRef value) { | |
1286 | LOGD("engine[%i]: Destroying %s", connection_get_pid(engine->conn), mechanism_get_string((mechanism_t)key)); | |
1287 | agent_t agent = (agent_t)value; | |
1288 | agent_destroy(agent); | |
1289 | ||
1290 | return true; | |
1291 | }); | |
1292 | } | |
1293 | ||
1294 | void engine_interrupt_agent(engine_t engine) | |
1295 | { | |
1296 | _cf_dictionary_iterate(engine->mechanism_agents, ^bool(CFTypeRef key __attribute__((__unused__)), CFTypeRef value) { | |
1297 | agent_t agent = (agent_t)value; | |
1298 | agent_notify_interrupt(agent); | |
1299 | return true; | |
1300 | }); | |
1301 | } |