2 * Copyright (c) 2003-2004,2008-2009 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
23 * AuthorizationRule.cpp
28 #include "AuthorizationRule.h"
29 #include <Security/AuthorizationTags.h>
30 #include <Security/AuthorizationTagsPriv.h>
31 #include <Security/AuthorizationDB.h>
32 #include <Security/AuthorizationPriv.h>
33 #include <security_utilities/logging.h>
34 #include <bsm/audit_uevents.h>
35 #include "ccaudit_extensions.h"
36 #include "authority.h"
39 #include "agentquery.h"
40 #include "AuthorizationMechEval.h"
46 #include <membership.h>
49 #include <membershipPriv.h>
52 using namespace CommonCriteria::Securityd
;
57 namespace Authorization
{
59 CFStringRef
RuleImpl::kUserGroupID
= CFSTR(kAuthorizationRuleParameterGroup
);
60 CFStringRef
RuleImpl::kTimeoutID
= CFSTR(kAuthorizationRuleParameterCredentialTimeout
);
61 CFStringRef
RuleImpl::kSharedID
= CFSTR(kAuthorizationRuleParameterCredentialShared
);
62 CFStringRef
RuleImpl::kAllowRootID
= CFSTR(kAuthorizationRuleParameterAllowRoot
);
63 CFStringRef
RuleImpl::kMechanismsID
= CFSTR(kAuthorizationRuleParameterMechanisms
);
64 CFStringRef
RuleImpl::kSessionOwnerID
= CFSTR(kAuthorizationRuleParameterCredentialSessionOwner
);
65 CFStringRef
RuleImpl::kKofNID
= CFSTR(kAuthorizationRuleParameterKofN
);
66 CFStringRef
RuleImpl::kPromptID
= CFSTR(kAuthorizationRuleParameterDefaultPrompt
);
67 CFStringRef
RuleImpl::kButtonID
= CFSTR(kAuthorizationRuleParameterDefaultButton
);
68 CFStringRef
RuleImpl::kTriesID
= CFSTR("tries"); // XXX/cs move to AuthorizationTagsPriv.h
69 CFStringRef
RuleImpl::kExtractPasswordID
= CFSTR(kAuthorizationRuleParameterExtractPassword
);
71 CFStringRef
RuleImpl::kRuleClassID
= CFSTR(kAuthorizationRuleClass
);
72 CFStringRef
RuleImpl::kRuleAllowID
= CFSTR(kAuthorizationRuleClassAllow
);
73 CFStringRef
RuleImpl::kRuleDenyID
= CFSTR(kAuthorizationRuleClassDeny
);
74 CFStringRef
RuleImpl::kRuleUserID
= CFSTR(kAuthorizationRuleClassUser
);
75 CFStringRef
RuleImpl::kRuleDelegateID
= CFSTR(kAuthorizationRightRule
);
76 CFStringRef
RuleImpl::kRuleMechanismsID
= CFSTR(kAuthorizationRuleClassMechanisms
);
77 CFStringRef
RuleImpl::kRuleAuthenticateUserID
= CFSTR(kAuthorizationRuleParameterAuthenticateUser
);
81 RuleImpl::Attribute::getString(CFDictionaryRef config
, CFStringRef key
, bool required
= false, const char *defaultValue
= "")
83 CFTypeRef value
= CFDictionaryGetValue(config
, key
);
84 if (value
&& (CFGetTypeID(value
) == CFStringGetTypeID()))
86 CFStringRef stringValue
= reinterpret_cast<CFStringRef
>(value
);
88 const char *ptr
= CFStringGetCStringPtr(stringValue
, kCFStringEncodingUTF8
);
91 if (CFStringGetCString(stringValue
, buffer
, sizeof(buffer
), kCFStringEncodingUTF8
))
95 Syslog::alert("Could not convert CFString to C string");
96 MacOSError::throwMe(errAuthorizationInternal
);
104 return string(defaultValue
);
107 Syslog::alert("Failed to get rule string");
108 MacOSError::throwMe(errAuthorizationInternal
);
113 RuleImpl::Attribute::getDouble(CFDictionaryRef config
, CFStringRef key
, bool required
= false, double defaultValue
= 0.0)
115 double doubleValue
= 0;
117 CFTypeRef value
= CFDictionaryGetValue(config
, key
);
118 if (value
&& (CFGetTypeID(value
) == CFNumberGetTypeID()))
120 CFNumberGetValue(reinterpret_cast<CFNumberRef
>(value
), kCFNumberDoubleType
, &doubleValue
);
127 Syslog::alert("Failed to get rule double value");
128 MacOSError::throwMe(errAuthorizationInternal
);
135 RuleImpl::Attribute::getBool(CFDictionaryRef config
, CFStringRef key
, bool required
= false, bool defaultValue
= false)
137 bool boolValue
= false;
138 CFTypeRef value
= CFDictionaryGetValue(config
, key
);
140 if (value
&& (CFGetTypeID(value
) == CFBooleanGetTypeID()))
142 boolValue
= CFBooleanGetValue(reinterpret_cast<CFBooleanRef
>(value
));
149 Syslog::alert("Failed to get rule bool value");
150 MacOSError::throwMe(errAuthorizationInternal
);
157 RuleImpl::Attribute::getVector(CFDictionaryRef config
, CFStringRef key
, bool required
= false)
159 vector
<string
> valueArray
;
161 CFTypeRef value
= CFDictionaryGetValue(config
, key
);
162 if (value
&& (CFGetTypeID(value
) == CFArrayGetTypeID()))
164 CFArrayRef evalArray
= reinterpret_cast<CFArrayRef
>(value
);
166 CFIndex numItems
= CFArrayGetCount(evalArray
);
167 for (CFIndex index
=0; index
< numItems
; index
++)
169 CFTypeRef arrayValue
= CFArrayGetValueAtIndex(evalArray
, index
);
170 if (arrayValue
&& (CFGetTypeID(arrayValue
) == CFStringGetTypeID()))
172 CFStringRef stringValue
= reinterpret_cast<CFStringRef
>(arrayValue
);
174 const char *ptr
= CFStringGetCStringPtr(stringValue
, kCFStringEncodingUTF8
);
177 if (CFStringGetCString(stringValue
, buffer
, sizeof(buffer
), kCFStringEncodingUTF8
))
181 Syslog::alert("Failed to convert CFString to C string for item %u in array", index
);
182 MacOSError::throwMe(errAuthorizationInternal
);
185 valueArray
.push_back(string(ptr
));
192 Syslog::alert("Value for key either not present or not a CFArray");
193 MacOSError::throwMe(errAuthorizationInternal
);
200 bool RuleImpl::Attribute::getLocalizedText(CFDictionaryRef config
, map
<string
,string
> &localizedPrompts
, CFStringRef dictKey
, const char *descriptionKey
)
202 CFIndex numberOfPrompts
= 0;
203 CFDictionaryRef promptsDict
;
204 if (CFDictionaryContainsKey(config
, dictKey
))
206 promptsDict
= reinterpret_cast<CFDictionaryRef
>(CFDictionaryGetValue(config
, dictKey
));
207 if (promptsDict
&& (CFGetTypeID(promptsDict
) == CFDictionaryGetTypeID()))
208 numberOfPrompts
= CFDictionaryGetCount(promptsDict
);
210 if (numberOfPrompts
== 0)
213 const void *keys
[numberOfPrompts
+1];
214 const void *values
[numberOfPrompts
+1];
215 CFDictionaryGetKeysAndValues(promptsDict
, &keys
[0], &values
[0]);
217 while (numberOfPrompts
-- > 0)
219 CFStringRef keyRef
= reinterpret_cast<CFStringRef
>(keys
[numberOfPrompts
]);
220 CFStringRef valueRef
= reinterpret_cast<CFStringRef
>(values
[numberOfPrompts
]);
221 if (!keyRef
|| (CFGetTypeID(keyRef
) != CFStringGetTypeID())) {
224 if (!valueRef
|| (CFGetTypeID(valueRef
) != CFStringGetTypeID())) {
227 string key
= cfString(keyRef
);
228 string value
= cfString(valueRef
);
229 localizedPrompts
[descriptionKey
+ key
] = value
;
237 RuleImpl::RuleImpl() :
238 mType(kUser
), mGroupName("admin"), mMaxCredentialAge(300.0), mShared(true), mAllowRoot(false), mSessionOwner(false), mTries(0), mAuthenticateUser(true), mExtractPassword(false)
240 // XXX/cs read default descriptions from somewhere
241 // @@@ Default rule is shared admin group with 5 minute timeout
244 // return rule built from rule definition; throw if invalid.
245 RuleImpl::RuleImpl(const string
&inRightName
, CFDictionaryRef cfRight
, CFDictionaryRef cfRules
) : mRightName(inRightName
), mExtractPassword(false)
247 // @@@ make sure cfRight is non mutable and never used that way
249 if (CFGetTypeID(cfRight
) != CFDictionaryGetTypeID())
251 Syslog::alert("Invalid rights set");
252 MacOSError::throwMe(errAuthorizationInternal
);
257 string classTag
= Attribute::getString(cfRight
, kRuleClassID
, false, "");
259 if (classTag
.length())
261 if (classTag
== kAuthorizationRuleClassAllow
)
263 secdebug("authrule", "%s : rule allow", inRightName
.c_str());
266 else if (classTag
== kAuthorizationRuleClassDeny
)
268 secdebug("authrule", "%s : rule deny", inRightName
.c_str());
271 else if (classTag
== kAuthorizationRuleClassUser
)
274 mGroupName
= Attribute::getString(cfRight
, kUserGroupID
);
275 // grab other user-in-group attributes
276 mMaxCredentialAge
= Attribute::getDouble(cfRight
, kTimeoutID
, false, DBL_MAX
);
277 mShared
= Attribute::getBool(cfRight
, kSharedID
);
278 mAllowRoot
= Attribute::getBool(cfRight
, kAllowRootID
);
279 mSessionOwner
= Attribute::getBool(cfRight
, kSessionOwnerID
);
280 // authorization tags can have eval now too
281 mEvalDef
= Attribute::getVector(cfRight
, kMechanismsID
);
282 if (mEvalDef
.size() == 0 && cfRules
/*only rights default see appserver-admin*/)
284 CFDictionaryRef cfRuleDef
= reinterpret_cast<CFDictionaryRef
>(CFDictionaryGetValue(cfRules
, CFSTR("authenticate")));
285 if (cfRuleDef
&& CFGetTypeID(cfRuleDef
) == CFDictionaryGetTypeID())
286 mEvalDef
= Attribute::getVector(cfRuleDef
, kMechanismsID
);
288 mTries
= int(Attribute::getDouble(cfRight
, kTriesID
, false, double(kMaximumAuthorizationTries
)));
289 mAuthenticateUser
= Attribute::getBool(cfRight
, kRuleAuthenticateUserID
, false, true);
290 mExtractPassword
= Attribute::getBool(cfRight
, kExtractPasswordID
, false, false);
292 secdebug("authrule", "%s : rule user in group \"%s\" timeout %g%s%s",
294 mGroupName
.c_str(), mMaxCredentialAge
, mShared
? " shared" : "",
295 mAllowRoot
? " allow-root" : "");
298 else if (classTag
== kAuthorizationRuleClassMechanisms
)
300 secdebug("authrule", "%s : rule evaluate mechanisms", inRightName
.c_str());
301 mType
= kEvaluateMechanisms
;
302 // mechanisms to evaluate
303 mEvalDef
= Attribute::getVector(cfRight
, kMechanismsID
, true);
304 mTries
= int(Attribute::getDouble(cfRight
, kTriesID
, false, 0.0)); // "forever"
305 mShared
= Attribute::getBool(cfRight
, kSharedID
, false, true);
306 mExtractPassword
= Attribute::getBool(cfRight
, kExtractPasswordID
, false, false);
308 else if (classTag
== kAuthorizationRightRule
)
310 assert(cfRules
); // rules can't delegate to other rules
311 secdebug("authrule", "%s : rule delegate rule", inRightName
.c_str());
312 mType
= kRuleDelegation
;
315 string ruleDefString
= Attribute::getString(cfRight
, kRuleDelegateID
, false, "");
316 if (ruleDefString
.length())
318 CFStringRef ruleDefRef
= makeCFString(ruleDefString
);
319 CFDictionaryRef cfRuleDef
= reinterpret_cast<CFDictionaryRef
>(CFDictionaryGetValue(cfRules
, ruleDefRef
));
321 CFRelease(ruleDefRef
);
322 if (!cfRuleDef
|| CFGetTypeID(cfRuleDef
) != CFDictionaryGetTypeID())
324 Syslog::alert("'%s' does not name a built-in rule", ruleDefString
.c_str());
325 MacOSError::throwMe(errAuthorizationInternal
);
327 mRuleDef
.push_back(Rule(ruleDefString
, cfRuleDef
, cfRules
));
331 vector
<string
> ruleDef
= Attribute::getVector(cfRight
, kRuleDelegateID
, true);
332 for (vector
<string
>::const_iterator it
= ruleDef
.begin(); it
!= ruleDef
.end(); it
++)
334 CFStringRef ruleNameRef
= makeCFString(*it
);
335 CFDictionaryRef cfRuleDef
= reinterpret_cast<CFDictionaryRef
>(CFDictionaryGetValue(cfRules
, ruleNameRef
));
337 CFRelease(ruleNameRef
);
338 if (!cfRuleDef
|| (CFGetTypeID(cfRuleDef
) != CFDictionaryGetTypeID()))
340 Syslog::alert("Invalid rule '%s'in rule set", it
->c_str());
341 MacOSError::throwMe(errAuthorizationInternal
);
343 mRuleDef
.push_back(Rule(*it
, cfRuleDef
, cfRules
));
347 mKofN
= int(Attribute::getDouble(cfRight
, kKofNID
, false, 0.0));
354 secdebug("authrule", "%s : rule class '%s' unknown.", inRightName
.c_str(), classTag
.c_str());
355 Syslog::alert("%s : rule class '%s' unknown", inRightName
.c_str(), classTag
.c_str());
356 MacOSError::throwMe(errAuthorizationInternal
);
361 // no class tag means, this is the abbreviated specification from the API
362 // it _must_ have a definition for "rule" which will be used as a delegate
363 // it may have a comment (not extracted here)
364 // it may have a default prompt, or a whole dictionary of languages (not extracted here)
365 mType
= kRuleDelegation
;
366 string ruleName
= Attribute::getString(cfRight
, kRuleDelegateID
, true);
367 secdebug("authrule", "%s : rule delegate rule (1): %s", inRightName
.c_str(), ruleName
.c_str());
368 CFStringRef ruleNameRef
= makeCFString(ruleName
);
369 CFDictionaryRef cfRuleDef
= reinterpret_cast<CFDictionaryRef
>(CFDictionaryGetValue(cfRules
, ruleNameRef
));
371 CFRelease(ruleNameRef
);
372 if (!cfRuleDef
|| CFGetTypeID(cfRuleDef
) != CFDictionaryGetTypeID())
374 Syslog::alert("Rule '%s' for right '%s' does not exist or is not properly formed", ruleName
.c_str(), inRightName
.c_str());
375 MacOSError::throwMe(errAuthorizationInternal
);
377 mRuleDef
.push_back(Rule(ruleName
, cfRuleDef
, cfRules
));
380 Attribute::getLocalizedText(cfRight
, mLocalizedPrompts
, kPromptID
, kAuthorizationRuleParameterDescription
);
381 Attribute::getLocalizedText(cfRight
, mLocalizedButtons
, kButtonID
, kAuthorizationRuleParameterButton
);
391 RuleImpl::setAgentHints(const AuthItemRef
&inRight
, const Rule
&inTopLevelRule
, AuthItemSet
&environmentToClient
, AuthorizationToken
&auth
) const
393 string
authorizeString(inRight
->name());
394 environmentToClient
.erase(AuthItemRef(AGENT_HINT_AUTHORIZE_RIGHT
));
395 environmentToClient
.insert(AuthItemRef(AGENT_HINT_AUTHORIZE_RIGHT
, AuthValueOverlay(authorizeString
)));
397 pid_t creatorPid
= auth
.creatorPid();
398 environmentToClient
.erase(AuthItemRef(AGENT_HINT_CREATOR_PID
));
399 environmentToClient
.insert(AuthItemRef(AGENT_HINT_CREATOR_PID
, AuthValueOverlay(sizeof(pid_t
), &creatorPid
)));
401 audit_token_t creatorAuditToken
= auth
.creatorAuditToken().auditToken();
402 environmentToClient
.erase(AuthItemRef(AGENT_HINT_CREATOR_AUDIT_TOKEN
));
403 environmentToClient
.insert(AuthItemRef(AGENT_HINT_CREATOR_AUDIT_TOKEN
, AuthValueOverlay(sizeof(audit_token_t
), &creatorAuditToken
)));
405 Process
&thisProcess
= Server::process();
407 if (SecStaticCodeRef clientCode
= auth
.creatorCode())
408 bundlePath
= codePath(clientCode
);
409 AuthItemSet processHints
= SecurityAgent::Client::clientHints(
410 SecurityAgent::bundle
, bundlePath
, thisProcess
.pid(), thisProcess
.uid());
411 environmentToClient
.erase(AuthItemRef(AGENT_HINT_CLIENT_TYPE
));
412 environmentToClient
.erase(AuthItemRef(AGENT_HINT_CLIENT_PATH
));
413 environmentToClient
.erase(AuthItemRef(AGENT_HINT_CLIENT_PID
));
414 environmentToClient
.erase(AuthItemRef(AGENT_HINT_CLIENT_UID
));
415 environmentToClient
.insert(processHints
.begin(), processHints
.end());
417 map
<string
,string
> defaultPrompts
= inTopLevelRule
->localizedPrompts();
418 map
<string
,string
> defaultButtons
= inTopLevelRule
->localizedButtons();
420 if (defaultPrompts
.empty())
421 defaultPrompts
= localizedPrompts();
422 if (defaultButtons
.empty())
423 defaultButtons
= localizedButtons();
425 if (!defaultPrompts
.empty())
427 map
<string
,string
>::const_iterator it
;
428 for (it
= defaultPrompts
.begin(); it
!= defaultPrompts
.end(); it
++)
430 const string
&key
= it
->first
;
431 const string
&value
= it
->second
;
432 environmentToClient
.insert(AuthItemRef(key
.c_str(), AuthValueOverlay(value
)));
435 if (!defaultButtons
.empty())
437 map
<string
,string
>::const_iterator it
;
438 for (it
= defaultButtons
.begin(); it
!= defaultButtons
.end(); it
++)
440 const string
&key
= it
->first
;
441 const string
&value
= it
->second
;
442 environmentToClient
.insert(AuthItemRef(key
.c_str(), AuthValueOverlay(value
)));
446 // add rulename as a hint
447 string ruleName
= name();
448 environmentToClient
.erase(AuthItemRef(AGENT_HINT_AUTHORIZE_RULE
));
449 environmentToClient
.insert(AuthItemRef(AGENT_HINT_AUTHORIZE_RULE
, AuthValueOverlay(ruleName
)));
452 // If a different evaluation for getting a credential is prescribed,
453 // we'll run that and validate the credentials from there.
454 // we fall back on a default configuration from the authenticate rule
456 RuleImpl::evaluateAuthentication(const AuthItemRef
&inRight
, const Rule
&inRule
,AuthItemSet
&environmentToClient
, AuthorizationFlags flags
, CFAbsoluteTime now
, const CredentialSet
*inCredentials
, CredentialSet
&credentials
, AuthorizationToken
&auth
, SecurityAgent::Reason
&reason
, bool savePassword
) const
458 OSStatus status
= errAuthorizationDenied
;
460 Credential hintCredential
;
461 if (errAuthorizationSuccess
== evaluateSessionOwner(inRight
, inRule
, environmentToClient
, now
, auth
, hintCredential
, reason
)) {
462 if (hintCredential
->name().length())
463 environmentToClient
.insert(AuthItemRef(AGENT_HINT_SUGGESTED_USER
, AuthValueOverlay(hintCredential
->name())));
464 if (hintCredential
->realname().length())
465 environmentToClient
.insert(AuthItemRef(AGENT_HINT_SUGGESTED_USER_LONG
, AuthValueOverlay(hintCredential
->realname())));
468 if ((mType
== kUser
) && (mGroupName
.length()))
469 environmentToClient
.insert(AuthItemRef(AGENT_HINT_REQUIRE_USER_IN_GROUP
, AuthValueOverlay(mGroupName
)));
472 reason
= SecurityAgent::noReason
;
474 Process
&cltProc
= Server::process();
475 // Authorization preserves creator's UID in setuid processes
476 // (which is nice, but cltUid ends up being unused except by the debug
477 // message -- AgentMechanismEvaluator ignores it)
478 uid_t cltUid
= (cltProc
.uid() != 0) ? cltProc
.uid() : auth
.creatorUid();
479 secdebug("AuthEvalMech", "Mechanism invocation by process %d (UID %d)", cltProc
.pid(), cltUid
);
481 // For auditing within AuthorizationMechEval, pass the right name.
482 size_t rightNameSize
= inRight
->name() ? strlen(inRight
->name()) : 0;
483 AuthorizationString rightName
= inRight
->name() ? inRight
->name() : "";
484 // @@@ AuthValueRef's ctor ought to take a const void *
485 AuthValueRef
rightValue(rightNameSize
, const_cast<char *>(rightName
));
486 AuthValueVector authValueVector
;
487 authValueVector
.push_back(rightValue
);
489 RightAuthenticationLogger
rightAuthLogger(auth
.creatorAuditToken(), AUE_ssauthint
);
490 rightAuthLogger
.setRight(rightName
);
492 // Just succeed for a continuously active session owner.
493 if (auth
.session().originatorUid() == auth
.creatorUid() && auth
.session().attributes() & AU_SESSION_FLAG_HAS_AUTHENTICATED
) {
494 secdebug("AuthEvalMech", "We are an active session owner.");
495 aslmsg m
= asl_new(ASL_TYPE_MSG
);
496 asl_set(m
, "com.apple.message.domain", "com.apple.securityd.UserActivity");
497 asl_set(m
, "com.apple.message.signature", "userIsActive");
498 asl_set(m
, "com.apple.message.signature2", rightName
);
499 asl_set(m
, "com.apple.message.result", "failure");
500 asl_log(NULL
, m
, ASL_LEVEL_NOTICE
, "We are an active session owner.");
502 // Credential rightCredential(rightName, auth.creatorUid(), mShared);
503 // credentials.erase(rightCredential); credentials.insert(rightCredential);
504 // return errAuthorizationSuccess;
507 secdebug("AuthEvalMech", "We are not an active session owner.");
508 aslmsg m
= asl_new(ASL_TYPE_MSG
);
509 asl_set(m
, "com.apple.message.domain", "com.apple.securityd.UserActivity");
510 asl_set(m
, "com.apple.message.signature", "userIsNotActive");
511 asl_set(m
, "com.apple.message.signature2", rightName
);
512 asl_set(m
, "com.apple.message.result", "success");
513 asl_log(NULL
, m
, ASL_LEVEL_NOTICE
, "We are not an active session owner.");
517 AgentMechanismEvaluator
eval(cltUid
, auth
.session(), mEvalDef
);
519 for (tries
= 0; tries
< mTries
; tries
++)
521 AuthItemRef
retryHint(AGENT_HINT_RETRY_REASON
, AuthValueOverlay(sizeof(reason
), &reason
));
522 environmentToClient
.erase(retryHint
); environmentToClient
.insert(retryHint
); // replace
523 AuthItemRef
triesHint(AGENT_HINT_TRIES
, AuthValueOverlay(sizeof(tries
), &tries
));
524 environmentToClient
.erase(triesHint
); environmentToClient
.insert(triesHint
); // replace
526 status
= eval
.run(authValueVector
, environmentToClient
, auth
);
528 if ((status
== errAuthorizationSuccess
) ||
529 (status
== errAuthorizationCanceled
)) // @@@ can only pass back sideband through context
531 secdebug("AuthEvalMech", "storing new context for authorization");
532 auth
.setInfoSet(eval
.context(), savePassword
);
535 // successfully ran mechanisms to obtain credential
536 if (status
== errAuthorizationSuccess
)
538 // deny is the default
539 status
= errAuthorizationDenied
;
541 CredentialSet newCredentials
= makeCredentials(auth
);
542 // clear context after extracting credentials
543 auth
.scrubInfoSet(savePassword
);
545 for (CredentialSet::const_iterator it
= newCredentials
.begin(); it
!= newCredentials
.end(); ++it
)
547 const Credential
& newCredential
= *it
;
549 // @@@ we log the uid a process was running under when it created the authref, which is misleading in the case of loginwindow
550 if (newCredential
->isValid()) {
551 Syslog::info("UID %u authenticated as user %s (UID %u) for right '%s'", auth
.creatorUid(), newCredential
->name().c_str(), newCredential
->uid(), rightName
);
552 rightAuthLogger
.logSuccess(auth
.creatorUid(), newCredential
->uid(), newCredential
->name().c_str());
554 // we can't be sure that the user actually exists so inhibit logging of uid
555 Syslog::error("UID %u failed to authenticate as user '%s' for right '%s'", auth
.creatorUid(), newCredential
->name().c_str(), rightName
);
556 rightAuthLogger
.logFailure(auth
.creatorUid(), newCredential
->name().c_str());
559 if (!newCredential
->isValid())
561 reason
= SecurityAgent::invalidPassphrase
;
565 // verify that this credential authorizes right
566 status
= evaluateUserCredentialForRight(auth
, inRight
, inRule
, environmentToClient
, now
, newCredential
, true, reason
);
568 if (status
== errAuthorizationSuccess
)
570 if (auth
.operatesAsLeastPrivileged()) {
571 Credential
rightCredential(rightName
, mShared
);
572 credentials
.erase(rightCredential
); credentials
.insert(rightCredential
);
574 credentials
.insert(Credential(rightName
, false));
577 // whack an equivalent credential, so it gets updated to a later achieved credential which must have been more stringent
578 credentials
.erase(newCredential
); credentials
.insert(newCredential
);
579 // just got a new credential - if it's shared also add a non-shared one that to stick in the authorizationref local cache
581 credentials
.insert(Credential(newCredential
->uid(), newCredential
->name(), newCredential
->realname(), false));
583 // use valid credential to set context info
584 // XXX/cs keeping this for now, such that the uid is passed back
585 auth
.setCredentialInfo(newCredential
, savePassword
);
586 secdebug("SSevalMech", "added valid credential for user %s", newCredential
->name().c_str());
587 // set the sessionHasAuthenticated
588 if (newCredential
->uid() == auth
.session().originatorUid()) {
589 secdebug("AuthEvalMech", "We authenticated as the session owner.\n");
590 SessionAttributeBits flags
= auth
.session().attributes();
591 flags
|= AU_SESSION_FLAG_HAS_AUTHENTICATED
;
592 auth
.session().setAttributes(flags
);
595 status
= errAuthorizationSuccess
;
600 if (status
== errAuthorizationSuccess
)
604 if ((status
== errAuthorizationCanceled
) || (status
== errAuthorizationInternal
))
606 auth
.scrubInfoSet(false);
609 else // last mechanism is now authentication - fail
610 if (status
== errAuthorizationDenied
)
611 reason
= SecurityAgent::invalidPassphrase
;
614 // If we fell out of the loop because of too many tries, notify user
617 reason
= SecurityAgent::tooManyTries
;
618 AuthItemRef
retryHint (AGENT_HINT_RETRY_REASON
, AuthValueOverlay(sizeof(reason
), &reason
));
619 environmentToClient
.erase(retryHint
); environmentToClient
.insert(retryHint
); // replace
620 AuthItemRef
triesHint(AGENT_HINT_TRIES
, AuthValueOverlay(sizeof(tries
), &tries
));
621 environmentToClient
.erase(triesHint
); environmentToClient
.insert(triesHint
); // replace
622 eval
.run(AuthValueVector(), environmentToClient
, auth
);
623 // XXX/cs is this still necessary?
624 auth
.scrubInfoSet(false);
626 rightAuthLogger
.logFailure(NULL
, CommonCriteria::errTooManyTries
);
632 // create externally verified credentials on the basis of
633 // mechanism-provided information
635 RuleImpl::makeCredentials(const AuthorizationToken
&auth
) const
637 // fetch context and construct a credential to be tested
638 const AuthItemSet
&context
= const_cast<AuthorizationToken
&>(auth
).infoSet();
639 CredentialSet newCredentials
;
642 AuthItemSet::const_iterator found
= find_if(context
.begin(), context
.end(), FindAuthItemByRightName(kAuthorizationEnvironmentUsername
) );
643 if (found
== context
.end())
645 string username
= (**found
).stringValue();
646 secdebug("AuthEvalMech", "found username");
648 const uid_t
*uid
= NULL
;
649 found
= find_if(context
.begin(), context
.end(), FindAuthItemByRightName("uid") );
650 if (found
!= context
.end())
652 uid
= static_cast<const uid_t
*>((**found
).value().data
);
653 secdebug("AuthEvalMech", "found uid");
656 if (username
.length() && uid
)
658 // credential is valid because mechanism says so
659 newCredentials
.insert(Credential(*uid
, username
, "", mShared
));
663 return newCredentials
;
666 // evaluate whether a good credential of the current session owner would authorize a right
668 RuleImpl::evaluateSessionOwner(const AuthItemRef
&inRight
, const Rule
&inRule
, const AuthItemSet
&environment
, const CFAbsoluteTime now
, const AuthorizationToken
&auth
, Credential
&credential
, SecurityAgent::Reason
&reason
) const
670 // username hint is taken from the user who created the authorization, unless it's clearly ineligible
671 // @@@ we have no access to current requester uid here and the process uid is only taken when the authorization is created
672 // meaning that a process like loginwindow that drops privs later is screwed.
674 Credential sessionCredential
;
675 uid_t uid
= auth
.session().originatorUid();
676 Server::active().longTermActivity();
677 struct passwd
*pw
= getpwuid(uid
);
679 // avoid hinting a locked account
680 if ( (pw
->pw_passwd
== NULL
) ||
681 strcmp(pw
->pw_passwd
, "*") ) {
682 // Check if username will authorize the request and set username to
683 // be used as a hint to the user if so
684 secdebug("AuthEvalMech", "preflight credential from current user, result follows:");
685 sessionCredential
= Credential(pw
->pw_uid
, pw
->pw_name
, pw
->pw_gecos
, mShared
/*ignored*/);
689 OSStatus status
= evaluateUserCredentialForRight(auth
, inRight
, inRule
, environment
, now
, sessionCredential
, true, reason
);
690 if (errAuthorizationSuccess
== status
)
691 credential
= sessionCredential
;
698 RuleImpl::evaluateCredentialForRight(const AuthorizationToken
&auth
, const AuthItemRef
&inRight
, const Rule
&inRule
, const AuthItemSet
&environment
, CFAbsoluteTime now
, const Credential
&credential
, bool ignoreShared
, SecurityAgent::Reason
&reason
) const
700 if (auth
.operatesAsLeastPrivileged()) {
701 if (credential
->isRight() && credential
->isValid() && (inRight
->name() == credential
->name()))
703 if (!ignoreShared
&& !mShared
&& credential
->isShared())
705 // @@@ no proper SA::Reason
706 reason
= SecurityAgent::unknownReason
;
707 secdebug("autheval", "shared credential cannot be used, denying right %s", inRight
->name());
708 return errAuthorizationDenied
;
710 return errAuthorizationSuccess
;
713 // @@@ no proper SA::Reason
714 reason
= SecurityAgent::unknownReason
;
715 return errAuthorizationDenied
;
718 return evaluateUserCredentialForRight(auth
, inRight
, inRule
, environment
, now
, credential
, false, reason
);
721 // Return errAuthorizationSuccess if this rule allows access based on the specified credential,
722 // return errAuthorizationDenied otherwise.
724 RuleImpl::evaluateUserCredentialForRight(const AuthorizationToken
&auth
, const AuthItemRef
&inRight
, const Rule
&inRule
, const AuthItemSet
&environment
, CFAbsoluteTime now
, const Credential
&credential
, bool ignoreShared
, SecurityAgent::Reason
&reason
) const
726 assert(mType
== kUser
);
728 // Ideally we'd set the AGENT_HINT_RETRY_REASON hint in this method, but
729 // evaluateAuthentication() overwrites it before
730 // AgentMechanismEvaluator::run(). That's what led to passing "reason"
731 // everywhere, from RuleImpl::evaluate() on down.
733 // Get the username from the credential
734 const char *user
= credential
->name().c_str();
736 // If the credential is not valid or its age is more than the allowed maximum age
737 // for a credential, deny.
738 if (!credential
->isValid())
740 // @@@ it could be the username, not password, was invalid
741 reason
= SecurityAgent::invalidPassphrase
;
742 secdebug("autheval", "credential for user %s is invalid, denying right %s", user
, inRight
->name());
743 return errAuthorizationDenied
;
746 if (now
- credential
->creationTime() > mMaxCredentialAge
)
748 // @@@ no proper SA::Reason
749 reason
= SecurityAgent::unknownReason
;
750 secdebug("autheval", "credential for user %s has expired, denying right %s", user
, inRight
->name());
751 return errAuthorizationDenied
;
754 if (!ignoreShared
&& !mShared
&& credential
->isShared())
756 // @@@ no proper SA::Reason
757 reason
= SecurityAgent::unknownReason
;
758 secdebug("autheval", "shared credential for user %s cannot be used, denying right %s", user
, inRight
->name());
759 return errAuthorizationDenied
;
762 // A root (uid == 0) user can do anything
763 if (credential
->uid() == 0)
765 secdebug("autheval", "user %s has uid 0, granting right %s", user
, inRight
->name());
766 return errAuthorizationSuccess
;
771 Session
&session
= auth
.session();
772 uid_t console_user
= session
.originatorUid();
774 if (credential
->uid() == console_user
)
776 secdebug("autheval", "user %s is session-owner(uid: %d), granting right %s", user
, console_user
, inRight
->name());
777 return errAuthorizationSuccess
;
779 // set "reason" in this case? not that a proper SA::Reason exists
783 // @@@ no proper SA::Reason
784 reason
= SecurityAgent::unknownReason
;
785 secdebug("autheval", "session-owner check failed.");
788 if (mGroupName
.length())
790 const char *groupname
= mGroupName
.c_str();
791 Server::active().longTermActivity();
794 return errAuthorizationDenied
;
798 uuid_t group_uuid
, user_uuid
;
801 // @@@ it'd be nice to have SA::Reason codes for the failures
802 // associated with the pre-check-membership mbr_*() functions,
803 // but userNotInGroup will do
804 if (mbr_group_name_to_uuid(groupname
, group_uuid
))
807 if (mbr_uid_to_uuid(credential
->uid(), user_uuid
))
810 if (NULL
== (pwd
= getpwnam(user
)))
812 if (mbr_uid_to_uuid(pwd
->pw_uid
, user_uuid
))
816 if (mbr_check_membership(user_uuid
, group_uuid
, &is_member
))
821 secdebug("autheval", "user %s is a member of group %s, granting right %s",
822 user
, groupname
, inRight
->name());
823 return errAuthorizationSuccess
;
829 reason
= SecurityAgent::userNotInGroup
;
830 secdebug("autheval", "user %s is not a member of group %s, denying right %s",
831 user
, groupname
, inRight
->name());
833 else if (mSessionOwner
) // rule asks only if user is the session owner
835 reason
= SecurityAgent::unacceptableUser
;
838 return errAuthorizationDenied
;
844 RuleImpl::evaluateUser(const AuthItemRef
&inRight
, const Rule
&inRule
, AuthItemSet
&environmentToClient
, AuthorizationFlags flags
, CFAbsoluteTime now
, const CredentialSet
*inCredentials
, CredentialSet
&credentials
, AuthorizationToken
&auth
, SecurityAgent::Reason
&reason
, bool savePassword
) const
846 // If we got here, this is a kUser type rule, let's start looking for a
847 // credential that is satisfactory
849 // Zeroth -- Here is an extra special saucy ugly hack to allow authorizations
850 // created by a proccess running as root to automatically get a right.
851 if (mAllowRoot
&& auth
.creatorUid() == 0)
853 SECURITYD_AUTH_USER_ALLOWROOT(&auth
);
855 secdebug("autheval", "creator of authorization has uid == 0 granting right %s",
857 return errAuthorizationSuccess
;
860 // if we're not supposed to authenticate evaluate the session-owner against the group
861 if (!mAuthenticateUser
)
863 Credential hintCredential
;
864 OSStatus status
= evaluateSessionOwner(inRight
, inRule
, environmentToClient
, now
, auth
, hintCredential
, reason
);
868 SECURITYD_AUTH_USER_ALLOWSESSIONOWNER(&auth
);
869 return errAuthorizationSuccess
;
872 return errAuthorizationDenied
;
875 // First -- go though the credentials we either already used or obtained during this authorize operation.
876 for (CredentialSet::const_iterator it
= credentials
.begin(); it
!= credentials
.end(); ++it
)
878 // Passed-in user credentials are allowed for least-privileged mode
879 if (auth
.operatesAsLeastPrivileged() && !(*it
)->isRight() && (*it
)->isValid())
881 OSStatus status
= evaluateUserCredentialForRight(auth
, inRight
, inRule
, environmentToClient
, now
, *it
, false, reason
);
882 if (errAuthorizationSuccess
== status
) {
883 Credential
rightCredential(inRight
->name(), mShared
);
884 credentials
.erase(rightCredential
); credentials
.insert(rightCredential
);
886 credentials
.insert(Credential(inRight
->name(), false));
891 // if this is least privileged, this will function differently: match credential to requested right
892 OSStatus status
= evaluateCredentialForRight(auth
, inRight
, inRule
, environmentToClient
, now
, *it
, false, reason
);
894 if (status
!= errAuthorizationDenied
) {
895 // add credential to authinfo
896 auth
.setCredentialInfo(*it
, savePassword
);
902 // Second -- go though the credentials passed in to this authorize operation by the state management layer.
905 for (CredentialSet::const_iterator it
= inCredentials
->begin(); it
!= inCredentials
->end(); ++it
)
907 // if this is least privileged, this will function differently: match credential to requested right
908 OSStatus status
= evaluateCredentialForRight(auth
, inRight
, inRule
, environmentToClient
, now
, *it
, false, reason
);
910 if (status
== errAuthorizationSuccess
)
912 // Add the credential we used to the output set.
913 // whack an equivalent credential, so it gets updated to a later achieved credential which must have been more stringent
914 credentials
.erase(*it
); credentials
.insert(*it
);
915 // add credential to authinfo
916 auth
.setCredentialInfo(*it
, savePassword
);
920 else if (status
!= errAuthorizationDenied
)
925 // Finally -- We didn't find the credential in our passed in credential lists. Obtain a new credential if our flags let us do so.
926 if (!(flags
& kAuthorizationFlagExtendRights
))
927 return errAuthorizationDenied
;
929 // authorizations that timeout immediately cannot be preauthorized
930 if ((flags
& kAuthorizationFlagPreAuthorize
) &&
931 (mMaxCredentialAge
== 0.0))
933 inRight
->setFlags(inRight
->flags() | kAuthorizationFlagCanNotPreAuthorize
);
934 return errAuthorizationSuccess
;
937 if (!(flags
& kAuthorizationFlagInteractionAllowed
))
938 return errAuthorizationInteractionNotAllowed
;
940 setAgentHints(inRight
, inRule
, environmentToClient
, auth
);
942 return evaluateAuthentication(inRight
, inRule
, environmentToClient
, flags
, now
, inCredentials
, credentials
, auth
, reason
, savePassword
);
946 RuleImpl::evaluateMechanismOnly(const AuthItemRef
&inRight
, const Rule
&inRule
, AuthItemSet
&environmentToClient
, AuthorizationToken
&auth
, CredentialSet
&outCredentials
, bool savePassword
) const
951 Process
&cltProc
= Server::process();
952 // Authorization preserves creator's UID in setuid processes
953 uid_t cltUid
= (cltProc
.uid() != 0) ? cltProc
.uid() : auth
.creatorUid();
954 secdebug("AuthEvalMech", "Mechanism invocation by process %d (UID %d)", cltProc
.pid(), cltUid
);
957 AgentMechanismEvaluator
eval(cltUid
, auth
.session(), mEvalDef
);
958 // For auditing within AuthorizationMechEval, pass the right name.
959 size_t rightNameSize
= inRight
->name() ? strlen(inRight
->name()) : 0;
960 AuthorizationString rightName
= inRight
->name() ? inRight
->name() : "";
961 // @@@ AuthValueRef's ctor ought to take a const void *
962 AuthValueRef
rightValue(rightNameSize
, const_cast<char *>(rightName
));
963 AuthValueVector authValueVector
;
964 authValueVector
.push_back(rightValue
);
968 setAgentHints(inRight
, inRule
, environmentToClient
, auth
);
969 AuthItemRef
triesHint(AGENT_HINT_TRIES
, AuthValueOverlay(sizeof(tries
), &tries
));
970 environmentToClient
.erase(triesHint
); environmentToClient
.insert(triesHint
); // replace
972 status
= eval
.run(authValueVector
, environmentToClient
, auth
);
973 if ((status
== errAuthorizationSuccess
) ||
974 (status
== errAuthorizationCanceled
)) // @@@ can only pass back sideband through context
976 secdebug("AuthEvalMech", "storing new context for authorization");
977 auth
.setInfoSet(eval
.context(), savePassword
);
978 if (status
== errAuthorizationSuccess
)
980 // (try to) attach the authorizing UID to the least-priv cred
981 if (auth
.operatesAsLeastPrivileged())
983 outCredentials
.insert(Credential(rightName
, mShared
));
985 outCredentials
.insert(Credential(rightName
, false));
987 RightAuthenticationLogger
logger(auth
.creatorAuditToken(), AUE_ssauthint
);
988 logger
.setRight(rightName
);
990 AuthItem
*uidItem
= eval
.context().find(AGENT_CONTEXT_UID
);
994 memcpy(&authorizedUid
, uidItem
->value().data
, sizeof(authorizedUid
));
995 secdebug("AuthEvalMech", "generating least-privilege cred for '%s' authorized by UID %u", inRight
->name(), authorizedUid
);
996 logger
.logLeastPrivilege(authorizedUid
, true);
998 else // cltUid is better than nothing
1000 secdebug("AuthEvalMech", "generating least-privilege cred for '%s' with process- or auth-UID %u", inRight
->name(), cltUid
);
1001 logger
.logLeastPrivilege(cltUid
, false);
1005 if (0 == strcmp(rightName
, "system.login.console") && NULL
== eval
.context().find(AGENT_CONTEXT_AUTO_LOGIN
)) {
1006 secdebug("AuthEvalMech", "We logged in as the session owner.\n");
1007 SessionAttributeBits flags
= auth
.session().attributes();
1008 flags
|= AU_SESSION_FLAG_HAS_AUTHENTICATED
;
1009 auth
.session().setAttributes(flags
);
1011 CredentialSet newCredentials
= makeCredentials(auth
);
1012 outCredentials
.insert(newCredentials
.begin(), newCredentials
.end());
1018 while ((status
== errAuthorizationDenied
) // only if we have an expected failure we continue
1019 && ((mTries
== 0) // mTries == 0 means we try forever
1020 || ((mTries
> 0) // mTries > 0 means we try up to mTries times
1021 && (tries
< mTries
))));
1024 // HACK kill all hosts to free pages for low memory systems
1025 // (XXX/gh there should be a #define for this right)
1026 if (name() == "system.login.done")
1028 // one case where we don't want to mark the agents as "busy"
1029 QueryInvokeMechanism
query(securityAgent
, auth
.session());
1030 query
.terminateAgent();
1031 QueryInvokeMechanism
query2(privilegedAuthHost
, auth
.session());
1032 query2
.terminateAgent();
1039 RuleImpl::evaluateRules(const AuthItemRef
&inRight
, const Rule
&inRule
, AuthItemSet
&environmentToClient
, AuthorizationFlags flags
, CFAbsoluteTime now
, const CredentialSet
*inCredentials
, CredentialSet
&credentials
, AuthorizationToken
&auth
, SecurityAgent::Reason
&reason
, bool savePassword
) const
1041 // line up the rules to try
1042 if (!mRuleDef
.size())
1043 return errAuthorizationSuccess
;
1046 OSStatus status
= errAuthorizationSuccess
;
1047 vector
<Rule
>::const_iterator it
;
1049 for (it
= mRuleDef
.begin();it
!= mRuleDef
.end(); it
++)
1052 if ((mType
== kKofN
) && (count
== mKofN
))
1053 return errAuthorizationSuccess
;
1055 // get a rule and try it
1056 status
= (*it
)->evaluate(inRight
, inRule
, environmentToClient
, flags
, now
, inCredentials
, credentials
, auth
, reason
, savePassword
);
1058 // if status is cancel/internal error abort
1059 if ((status
== errAuthorizationCanceled
) || (status
== errAuthorizationInternal
))
1062 if (status
!= errAuthorizationSuccess
)
1064 // continue if we're only looking for k of n
1074 if ((mType
== kKofN
) && (status
== errAuthorizationSuccess
) && (count
< mKofN
))
1075 status
= errAuthorizationDenied
;
1077 return status
; // return the last failure
1082 RuleImpl::evaluate(const AuthItemRef
&inRight
, const Rule
&inRule
, AuthItemSet
&environmentToClient
, AuthorizationFlags flags
, CFAbsoluteTime now
, const CredentialSet
*inCredentials
, CredentialSet
&credentials
, AuthorizationToken
&auth
, SecurityAgent::Reason
&reason
, bool savePassword
) const
1087 SECURITYD_AUTH_ALLOW(&auth
, (char *)name().c_str());
1088 return errAuthorizationSuccess
;
1090 SECURITYD_AUTH_DENY(&auth
, (char *)name().c_str());
1091 return errAuthorizationDenied
;
1093 SECURITYD_AUTH_USER(&auth
, (char *)name().c_str());
1094 return evaluateUser(inRight
, inRule
, environmentToClient
, flags
, now
, inCredentials
, credentials
, auth
, reason
, savePassword
);
1095 case kRuleDelegation
:
1096 SECURITYD_AUTH_RULES(&auth
, (char *)name().c_str());
1097 return evaluateRules(inRight
, inRule
, environmentToClient
, flags
, now
, inCredentials
, credentials
, auth
, reason
, savePassword
);
1099 SECURITYD_AUTH_KOFN(&auth
, (char *)name().c_str());
1100 return evaluateRules(inRight
, inRule
, environmentToClient
, flags
, now
, inCredentials
, credentials
, auth
, reason
, savePassword
);
1101 case kEvaluateMechanisms
:
1102 SECURITYD_AUTH_MECHRULE(&auth
, (char *)name().c_str());
1103 // if we had a SecurityAgent::Reason code for "mechanism denied,"
1104 // it would make sense to pass down "reason"
1105 return evaluateMechanismOnly(inRight
, inRule
, environmentToClient
, auth
, credentials
, savePassword
);
1107 Syslog::alert("Unrecognized rule type %d", mType
);
1108 MacOSError::throwMe(errAuthorizationInternal
); // invalid rule
1112 Rule::Rule() : RefPointer
<RuleImpl
>(new RuleImpl()) {}
1113 Rule::Rule(const string
&inRightName
, CFDictionaryRef cfRight
, CFDictionaryRef cfRules
) : RefPointer
<RuleImpl
>(new RuleImpl(inRightName
, cfRight
, cfRules
)) {}
1117 } // end namespace Authorization