]> git.saurik.com Git - apple/securityd.git/blob - src/AuthorizationRule.cpp
securityd-36975.tar.gz
[apple/securityd.git] / src / AuthorizationRule.cpp
1 /*
2 * Copyright (c) 2003-2004,2008-2009 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 *
23 * AuthorizationRule.cpp
24 * Security
25 *
26 */
27
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"
37 #include "server.h"
38 #include "process.h"
39 #include "agentquery.h"
40 #include "AuthorizationMechEval.h"
41
42 #include <pwd.h>
43 #include <grp.h>
44 #include <unistd.h>
45 #include <membership.h>
46
47 extern "C" {
48 #include <membershipPriv.h>
49 }
50
51 using namespace CommonCriteria::Securityd;
52
53 //
54 // Rule class
55 //
56 namespace Authorization {
57
58 CFStringRef RuleImpl::kUserGroupID = CFSTR(kAuthorizationRuleParameterGroup);
59 CFStringRef RuleImpl::kTimeoutID = CFSTR(kAuthorizationRuleParameterCredentialTimeout);
60 CFStringRef RuleImpl::kSharedID = CFSTR(kAuthorizationRuleParameterCredentialShared);
61 CFStringRef RuleImpl::kAllowRootID = CFSTR(kAuthorizationRuleParameterAllowRoot);
62 CFStringRef RuleImpl::kMechanismsID = CFSTR(kAuthorizationRuleParameterMechanisms);
63 CFStringRef RuleImpl::kSessionOwnerID = CFSTR(kAuthorizationRuleParameterCredentialSessionOwner);
64 CFStringRef RuleImpl::kKofNID = CFSTR(kAuthorizationRuleParameterKofN);
65 CFStringRef RuleImpl::kPromptID = CFSTR(kAuthorizationRuleParameterDefaultPrompt);
66 CFStringRef RuleImpl::kTriesID = CFSTR("tries"); // XXX/cs move to AuthorizationTagsPriv.h
67
68 CFStringRef RuleImpl::kRuleClassID = CFSTR(kAuthorizationRuleClass);
69 CFStringRef RuleImpl::kRuleAllowID = CFSTR(kAuthorizationRuleClassAllow);
70 CFStringRef RuleImpl::kRuleDenyID = CFSTR(kAuthorizationRuleClassDeny);
71 CFStringRef RuleImpl::kRuleUserID = CFSTR(kAuthorizationRuleClassUser);
72 CFStringRef RuleImpl::kRuleDelegateID = CFSTR(kAuthorizationRightRule);
73 CFStringRef RuleImpl::kRuleMechanismsID = CFSTR(kAuthorizationRuleClassMechanisms);
74 CFStringRef RuleImpl::kRuleAuthenticateUserID = CFSTR(kAuthorizationRuleParameterAuthenticateUser);
75
76
77 string
78 RuleImpl::Attribute::getString(CFDictionaryRef config, CFStringRef key, bool required = false, const char *defaultValue = "")
79 {
80 CFTypeRef value = CFDictionaryGetValue(config, key);
81 if (value && (CFGetTypeID(value) == CFStringGetTypeID()))
82 {
83 CFStringRef stringValue = reinterpret_cast<CFStringRef>(value);
84 char buffer[512];
85 const char *ptr = CFStringGetCStringPtr(stringValue, kCFStringEncodingUTF8);
86 if (ptr == NULL)
87 {
88 if (CFStringGetCString(stringValue, buffer, sizeof(buffer), kCFStringEncodingUTF8))
89 ptr = buffer;
90 else
91 {
92 Syslog::alert("Could not convert CFString to C string");
93 MacOSError::throwMe(errAuthorizationInternal);
94 }
95 }
96
97 return string(ptr);
98 }
99 else
100 if (!required)
101 return string(defaultValue);
102 else
103 {
104 Syslog::alert("Failed to get rule string");
105 MacOSError::throwMe(errAuthorizationInternal);
106 }
107 }
108
109 double
110 RuleImpl::Attribute::getDouble(CFDictionaryRef config, CFStringRef key, bool required = false, double defaultValue = 0.0)
111 {
112 double doubleValue = 0;
113
114 CFTypeRef value = CFDictionaryGetValue(config, key);
115 if (value && (CFGetTypeID(value) == CFNumberGetTypeID()))
116 {
117 CFNumberGetValue(reinterpret_cast<CFNumberRef>(value), kCFNumberDoubleType, &doubleValue);
118 }
119 else
120 if (!required)
121 return defaultValue;
122 else
123 {
124 Syslog::alert("Failed to get rule double value");
125 MacOSError::throwMe(errAuthorizationInternal);
126 }
127
128 return doubleValue;
129 }
130
131 bool
132 RuleImpl::Attribute::getBool(CFDictionaryRef config, CFStringRef key, bool required = false, bool defaultValue = false)
133 {
134 bool boolValue = false;
135 CFTypeRef value = CFDictionaryGetValue(config, key);
136
137 if (value && (CFGetTypeID(value) == CFBooleanGetTypeID()))
138 {
139 boolValue = CFBooleanGetValue(reinterpret_cast<CFBooleanRef>(value));
140 }
141 else
142 if (!required)
143 return defaultValue;
144 else
145 {
146 Syslog::alert("Failed to get rule bool value");
147 MacOSError::throwMe(errAuthorizationInternal);
148 }
149
150 return boolValue;
151 }
152
153 vector<string>
154 RuleImpl::Attribute::getVector(CFDictionaryRef config, CFStringRef key, bool required = false)
155 {
156 vector<string> valueArray;
157
158 CFTypeRef value = CFDictionaryGetValue(config, key);
159 if (value && (CFGetTypeID(value) == CFArrayGetTypeID()))
160 {
161 CFArrayRef evalArray = reinterpret_cast<CFArrayRef>(value);
162
163 CFIndex numItems = CFArrayGetCount(evalArray);
164 for (CFIndex index=0; index < numItems; index++)
165 {
166 CFTypeRef arrayValue = CFArrayGetValueAtIndex(evalArray, index);
167 if (arrayValue && (CFGetTypeID(arrayValue) == CFStringGetTypeID()))
168 {
169 CFStringRef stringValue = reinterpret_cast<CFStringRef>(arrayValue);
170 char buffer[512];
171 const char *ptr = CFStringGetCStringPtr(stringValue, kCFStringEncodingUTF8);
172 if (ptr == NULL)
173 {
174 if (CFStringGetCString(stringValue, buffer, sizeof(buffer), kCFStringEncodingUTF8))
175 ptr = buffer;
176 else
177 {
178 Syslog::alert("Failed to convert CFString to C string for item %u in array", index);
179 MacOSError::throwMe(errAuthorizationInternal);
180 }
181 }
182 valueArray.push_back(string(ptr));
183 }
184 }
185 }
186 else
187 if (required)
188 {
189 Syslog::alert("Value for key either not present or not a CFArray");
190 MacOSError::throwMe(errAuthorizationInternal);
191 }
192
193 return valueArray;
194 }
195
196
197 bool RuleImpl::Attribute::getLocalizedPrompts(CFDictionaryRef config, map<string,string> &localizedPrompts)
198 {
199 CFIndex numberOfPrompts = 0;
200 CFDictionaryRef promptsDict;
201 if (CFDictionaryContainsKey(config, kPromptID))
202 {
203 promptsDict = reinterpret_cast<CFDictionaryRef>(CFDictionaryGetValue(config, kPromptID));
204 if (promptsDict && (CFGetTypeID(promptsDict) == CFDictionaryGetTypeID()))
205 numberOfPrompts = CFDictionaryGetCount(promptsDict);
206 }
207 if (numberOfPrompts == 0)
208 return false;
209
210 const void *keys[numberOfPrompts+1];
211 const void *values[numberOfPrompts+1];
212 CFDictionaryGetKeysAndValues(promptsDict, &keys[0], &values[0]);
213
214 while (numberOfPrompts-- > 0)
215 {
216 CFStringRef keyRef = reinterpret_cast<CFStringRef>(keys[numberOfPrompts]);
217 CFStringRef valueRef = reinterpret_cast<CFStringRef>(values[numberOfPrompts]);
218 if (!keyRef || (CFGetTypeID(keyRef) != CFStringGetTypeID()))
219 continue;
220 if (!valueRef || (CFGetTypeID(valueRef) != CFStringGetTypeID()))
221 continue;
222 string key = cfString(keyRef);
223 string value = cfString(valueRef);
224 localizedPrompts[kAuthorizationRuleParameterDescription+key] = value;
225 }
226
227 return true;
228 }
229
230
231 // default rule
232 RuleImpl::RuleImpl() :
233 mType(kUser), mGroupName("admin"), mMaxCredentialAge(300.0), mShared(true), mAllowRoot(false), mSessionOwner(false), mTries(0), mAuthenticateUser(true)
234 {
235 // XXX/cs read default descriptions from somewhere
236 // @@@ Default rule is shared admin group with 5 minute timeout
237 }
238
239 // return rule built from rule definition; throw if invalid.
240 RuleImpl::RuleImpl(const string &inRightName, CFDictionaryRef cfRight, CFDictionaryRef cfRules) : mRightName(inRightName)
241 {
242 // @@@ make sure cfRight is non mutable and never used that way
243
244 if (CFGetTypeID(cfRight) != CFDictionaryGetTypeID())
245 {
246 Syslog::alert("Invalid rights set");
247 MacOSError::throwMe(errAuthorizationInternal);
248 }
249
250 mTries = 0;
251
252 string classTag = Attribute::getString(cfRight, kRuleClassID, false, "");
253
254 if (classTag.length())
255 {
256 if (classTag == kAuthorizationRuleClassAllow)
257 {
258 secdebug("authrule", "%s : rule allow", inRightName.c_str());
259 mType = kAllow;
260 }
261 else if (classTag == kAuthorizationRuleClassDeny)
262 {
263 secdebug("authrule", "%s : rule deny", inRightName.c_str());
264 mType = kDeny;
265 }
266 else if (classTag == kAuthorizationRuleClassUser)
267 {
268 mType = kUser;
269 mGroupName = Attribute::getString(cfRight, kUserGroupID);
270 // grab other user-in-group attributes
271 mMaxCredentialAge = Attribute::getDouble(cfRight, kTimeoutID, false, DBL_MAX);
272 mShared = Attribute::getBool(cfRight, kSharedID);
273 mAllowRoot = Attribute::getBool(cfRight, kAllowRootID);
274 mSessionOwner = Attribute::getBool(cfRight, kSessionOwnerID);
275 // authorization tags can have eval now too
276 mEvalDef = Attribute::getVector(cfRight, kMechanismsID);
277 if (mEvalDef.size() == 0 && cfRules /*only rights default see appserver-admin*/)
278 {
279 CFDictionaryRef cfRuleDef = reinterpret_cast<CFDictionaryRef>(CFDictionaryGetValue(cfRules, CFSTR("authenticate")));
280 if (cfRuleDef && CFGetTypeID(cfRuleDef) == CFDictionaryGetTypeID())
281 mEvalDef = Attribute::getVector(cfRuleDef, kMechanismsID);
282 }
283 mTries = int(Attribute::getDouble(cfRight, kTriesID, false, double(kMaximumAuthorizationTries)));
284 mAuthenticateUser = Attribute::getBool(cfRight, kRuleAuthenticateUserID, false, true);
285
286 secdebug("authrule", "%s : rule user in group \"%s\" timeout %g%s%s",
287 inRightName.c_str(),
288 mGroupName.c_str(), mMaxCredentialAge, mShared ? " shared" : "",
289 mAllowRoot ? " allow-root" : "");
290
291 }
292 else if (classTag == kAuthorizationRuleClassMechanisms)
293 {
294 secdebug("authrule", "%s : rule evaluate mechanisms", inRightName.c_str());
295 mType = kEvaluateMechanisms;
296 // mechanisms to evaluate
297 mEvalDef = Attribute::getVector(cfRight, kMechanismsID, true);
298 mTries = int(Attribute::getDouble(cfRight, kTriesID, false, 0.0)); // "forever"
299 mShared = Attribute::getBool(cfRight, kSharedID, false, true);
300 }
301 else if (classTag == kAuthorizationRightRule)
302 {
303 assert(cfRules); // rules can't delegate to other rules
304 secdebug("authrule", "%s : rule delegate rule", inRightName.c_str());
305 mType = kRuleDelegation;
306
307 // string or
308 string ruleDefString = Attribute::getString(cfRight, kRuleDelegateID, false, "");
309 if (ruleDefString.length())
310 {
311 CFStringRef ruleDefRef = makeCFString(ruleDefString);
312 CFDictionaryRef cfRuleDef = reinterpret_cast<CFDictionaryRef>(CFDictionaryGetValue(cfRules, ruleDefRef));
313 if (ruleDefRef)
314 CFRelease(ruleDefRef);
315 if (!cfRuleDef || CFGetTypeID(cfRuleDef) != CFDictionaryGetTypeID())
316 {
317 Syslog::alert("'%s' does not name a built-in rule", ruleDefString.c_str());
318 MacOSError::throwMe(errAuthorizationInternal);
319 }
320 mRuleDef.push_back(Rule(ruleDefString, cfRuleDef, cfRules));
321 }
322 else // array
323 {
324 vector<string> ruleDef = Attribute::getVector(cfRight, kRuleDelegateID, true);
325 for (vector<string>::const_iterator it = ruleDef.begin(); it != ruleDef.end(); it++)
326 {
327 CFStringRef ruleNameRef = makeCFString(*it);
328 CFDictionaryRef cfRuleDef = reinterpret_cast<CFDictionaryRef>(CFDictionaryGetValue(cfRules, ruleNameRef));
329 if (ruleNameRef)
330 CFRelease(ruleNameRef);
331 if (!cfRuleDef || (CFGetTypeID(cfRuleDef) != CFDictionaryGetTypeID()))
332 {
333 Syslog::alert("Invalid rule '%s'in rule set", it->c_str());
334 MacOSError::throwMe(errAuthorizationInternal);
335 }
336 mRuleDef.push_back(Rule(*it, cfRuleDef, cfRules));
337 }
338 }
339
340 mKofN = int(Attribute::getDouble(cfRight, kKofNID, false, 0.0));
341 if (mKofN)
342 mType = kKofN;
343
344 }
345 else
346 {
347 secdebug("authrule", "%s : rule class '%s' unknown.", inRightName.c_str(), classTag.c_str());
348 Syslog::alert("%s : rule class '%s' unknown", inRightName.c_str(), classTag.c_str());
349 MacOSError::throwMe(errAuthorizationInternal);
350 }
351 }
352 else
353 {
354 // no class tag means, this is the abbreviated specification from the API
355 // it _must_ have a definition for "rule" which will be used as a delegate
356 // it may have a comment (not extracted here)
357 // it may have a default prompt, or a whole dictionary of languages (not extracted here)
358 mType = kRuleDelegation;
359 string ruleName = Attribute::getString(cfRight, kRuleDelegateID, true);
360 secdebug("authrule", "%s : rule delegate rule (1): %s", inRightName.c_str(), ruleName.c_str());
361 CFStringRef ruleNameRef = makeCFString(ruleName);
362 CFDictionaryRef cfRuleDef = reinterpret_cast<CFDictionaryRef>(CFDictionaryGetValue(cfRules, ruleNameRef));
363 if (ruleNameRef)
364 CFRelease(ruleNameRef);
365 if (!cfRuleDef || CFGetTypeID(cfRuleDef) != CFDictionaryGetTypeID())
366 {
367 Syslog::alert("Rule '%s' for right '%s' does not exist or is not properly formed", ruleName.c_str(), inRightName.c_str());
368 MacOSError::throwMe(errAuthorizationInternal);
369 }
370 mRuleDef.push_back(Rule(ruleName, cfRuleDef, cfRules));
371 }
372
373 Attribute::getLocalizedPrompts(cfRight, mLocalizedPrompts);
374 }
375
376 /*
377 RuleImpl::~Rule()
378 {
379 }
380 */
381
382 void
383 RuleImpl::setAgentHints(const AuthItemRef &inRight, const Rule &inTopLevelRule, AuthItemSet &environmentToClient, AuthorizationToken &auth) const
384 {
385 string authorizeString(inRight->name());
386 environmentToClient.erase(AuthItemRef(AGENT_HINT_AUTHORIZE_RIGHT));
387 environmentToClient.insert(AuthItemRef(AGENT_HINT_AUTHORIZE_RIGHT, AuthValueOverlay(authorizeString)));
388
389 pid_t creatorPid = auth.creatorPid();
390 environmentToClient.erase(AuthItemRef(AGENT_HINT_CREATOR_PID));
391 environmentToClient.insert(AuthItemRef(AGENT_HINT_CREATOR_PID, AuthValueOverlay(sizeof(pid_t), &creatorPid)));
392
393 Process &thisProcess = Server::process();
394 string bundlePath;
395 if (SecStaticCodeRef clientCode = auth.creatorCode())
396 bundlePath = codePath(clientCode);
397 AuthItemSet processHints = SecurityAgent::Client::clientHints(
398 SecurityAgent::bundle, bundlePath, thisProcess.pid(), thisProcess.uid());
399 environmentToClient.erase(AuthItemRef(AGENT_HINT_CLIENT_TYPE));
400 environmentToClient.erase(AuthItemRef(AGENT_HINT_CLIENT_PATH));
401 environmentToClient.erase(AuthItemRef(AGENT_HINT_CLIENT_PID));
402 environmentToClient.erase(AuthItemRef(AGENT_HINT_CLIENT_UID));
403 environmentToClient.insert(processHints.begin(), processHints.end());
404
405 map<string,string> defaultPrompts = inTopLevelRule->localizedPrompts();
406
407 if (defaultPrompts.empty())
408 defaultPrompts = localizedPrompts();
409
410 if (!defaultPrompts.empty())
411 {
412 map<string,string>::const_iterator it;
413 for (it = defaultPrompts.begin(); it != defaultPrompts.end(); it++)
414 {
415 const string &key = it->first;
416 const string &value = it->second;
417 environmentToClient.insert(AuthItemRef(key.c_str(), AuthValueOverlay(value)));
418 }
419 }
420
421 // add rulename as a hint
422 string ruleName = name();
423 environmentToClient.erase(AuthItemRef(AGENT_HINT_AUTHORIZE_RULE));
424 environmentToClient.insert(AuthItemRef(AGENT_HINT_AUTHORIZE_RULE, AuthValueOverlay(ruleName)));
425 }
426
427 // If a different evaluation for getting a credential is prescribed,
428 // we'll run that and validate the credentials from there.
429 // we fall back on a default configuration from the authenticate rule
430 OSStatus
431 RuleImpl::evaluateAuthentication(const AuthItemRef &inRight, const Rule &inRule,AuthItemSet &environmentToClient, AuthorizationFlags flags, CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, AuthorizationToken &auth, SecurityAgent::Reason &reason) const
432 {
433 OSStatus status = errAuthorizationDenied;
434
435 Credential hintCredential;
436 if (errAuthorizationSuccess == evaluateSessionOwner(inRight, inRule, environmentToClient, now, auth, hintCredential, reason)) {
437 if (hintCredential->username().length())
438 environmentToClient.insert(AuthItemRef(AGENT_HINT_SUGGESTED_USER, AuthValueOverlay(hintCredential->username())));
439 if (hintCredential->realname().length())
440 environmentToClient.insert(AuthItemRef(AGENT_HINT_SUGGESTED_USER_LONG, AuthValueOverlay(hintCredential->realname())));
441 }
442
443 if ((mType == kUser) && (mGroupName.length()))
444 environmentToClient.insert(AuthItemRef(AGENT_HINT_REQUIRE_USER_IN_GROUP, AuthValueOverlay(mGroupName)));
445
446 uint32 tries;
447 reason = SecurityAgent::noReason;
448
449 Process &cltProc = Server::process();
450 // Authorization preserves creator's UID in setuid processes
451 // (which is nice, but cltUid ends up being unused except by the debug
452 // message -- AgentMechanismEvaluator ignores it)
453 uid_t cltUid = (cltProc.uid() != 0) ? cltProc.uid() : auth.creatorUid();
454 secdebug("AuthEvalMech", "Mechanism invocation by process %d (UID %d)", cltProc.pid(), cltUid);
455
456 // For auditing within AuthorizationMechEval, pass the right name.
457 size_t rightNameSize = inRight->name() ? strlen(inRight->name()) : 0;
458 AuthorizationString rightName = inRight->name() ? inRight->name() : "";
459 // @@@ AuthValueRef's ctor ought to take a const void *
460 AuthValueRef rightValue(rightNameSize, const_cast<char *>(rightName));
461 AuthValueVector authValueVector;
462 authValueVector.push_back(rightValue);
463
464 RightAuthenticationLogger rightAuthLogger(auth.creatorAuditToken(), AUE_ssauthint);
465 rightAuthLogger.setRight(rightName);
466
467 AgentMechanismEvaluator eval(cltUid, auth.session(), mEvalDef);
468
469 for (tries = 0; tries < mTries; tries++)
470 {
471 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
472 environmentToClient.erase(retryHint); environmentToClient.insert(retryHint); // replace
473 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(tries), &tries));
474 environmentToClient.erase(triesHint); environmentToClient.insert(triesHint); // replace
475
476 status = eval.run(authValueVector, environmentToClient, auth);
477
478 if ((status == errAuthorizationSuccess) ||
479 (status == errAuthorizationCanceled)) // @@@ can only pass back sideband through context
480 {
481 secdebug("AuthEvalMech", "storing new context for authorization");
482 auth.setInfoSet(eval.context());
483 }
484
485 // successfully ran mechanisms to obtain credential
486 if (status == errAuthorizationSuccess)
487 {
488 // deny is the default
489 status = errAuthorizationDenied;
490
491 CredentialSet newCredentials = makeCredentials(auth);
492 // clear context after extracting credentials
493 auth.scrubInfoSet();
494
495 for (CredentialSet::const_iterator it = newCredentials.begin(); it != newCredentials.end(); ++it)
496 {
497 const Credential& newCredential = *it;
498
499 // @@@ we log the uid a process was running under when it created the authref, which is misleading in the case of loginwindow
500 if (newCredential->isValid()) {
501 Syslog::info("UID %u authenticated as user %s (UID %u) for right '%s'", auth.creatorUid(), newCredential->username().c_str(), newCredential->uid(), rightName);
502 rightAuthLogger.logSuccess(auth.creatorUid(), newCredential->uid(), newCredential->username().c_str());
503 } else {
504 // we can't be sure that the user actually exists so inhibit logging of uid
505 Syslog::error("UID %u failed to authenticate as user '%s' for right '%s'", auth.creatorUid(), newCredential->username().c_str(), rightName);
506 rightAuthLogger.logFailure(auth.creatorUid(), newCredential->username().c_str());
507 }
508
509 if (!newCredential->isValid())
510 {
511 reason = SecurityAgent::invalidPassphrase;
512 continue;
513 }
514
515 // verify that this credential authorizes right
516 status = evaluateUserCredentialForRight(auth, inRight, inRule, environmentToClient, now, newCredential, true, reason);
517
518 if (status == errAuthorizationSuccess)
519 {
520 if (auth.operatesAsLeastPrivileged()) {
521 Credential rightCredential(rightName, newCredential->uid(), mShared);
522 credentials.erase(rightCredential); credentials.insert(rightCredential);
523 if (mShared)
524 credentials.insert(Credential(rightName, newCredential->uid(), false));
525 } else {
526 // whack an equivalent credential, so it gets updated to a later achieved credential which must have been more stringent
527 credentials.erase(newCredential); credentials.insert(newCredential);
528 // just got a new credential - if it's shared also add a non-shared one that to stick in the authorizationref local cache
529 if (mShared)
530 credentials.insert(Credential(newCredential->uid(), newCredential->username(), newCredential->realname(), newCredential->groupname(), false));
531 }
532
533 // use valid credential to set context info
534 // XXX/cs keeping this for now, such that the uid is passed back
535 auth.setCredentialInfo(newCredential);
536 secdebug("SSevalMech", "added valid credential for user %s", newCredential->username().c_str());
537 status = errAuthorizationSuccess;
538 break;
539 }
540 }
541
542 if (status == errAuthorizationSuccess)
543 break;
544 }
545 else
546 if ((status == errAuthorizationCanceled) || (status == errAuthorizationInternal))
547 {
548 auth.scrubInfoSet();
549 break;
550 }
551 else // last mechanism is now authentication - fail
552 if (status == errAuthorizationDenied)
553 reason = SecurityAgent::invalidPassphrase;
554 }
555
556 // If we fell out of the loop because of too many tries, notify user
557 if (tries == mTries)
558 {
559 reason = SecurityAgent::tooManyTries;
560 AuthItemRef retryHint (AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
561 environmentToClient.erase(retryHint); environmentToClient.insert(retryHint); // replace
562 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(tries), &tries));
563 environmentToClient.erase(triesHint); environmentToClient.insert(triesHint); // replace
564 eval.run(AuthValueVector(), environmentToClient, auth);
565 // XXX/cs is this still necessary?
566 auth.scrubInfoSet();
567
568 rightAuthLogger.logFailure(NULL, CommonCriteria::errTooManyTries);
569 }
570
571 return status;
572 }
573
574 // create externally verified credentials on the basis of
575 // mechanism-provided information
576 CredentialSet
577 RuleImpl::makeCredentials(const AuthorizationToken &auth) const
578 {
579 // fetch context and construct a credential to be tested
580 const AuthItemSet &context = const_cast<AuthorizationToken &>(auth).infoSet();
581 CredentialSet newCredentials;
582
583 do {
584 AuthItemSet::const_iterator found = find_if(context.begin(), context.end(), FindAuthItemByRightName(kAuthorizationEnvironmentUsername) );
585 if (found == context.end())
586 break;
587 string username = (**found).stringValue();
588 secdebug("AuthEvalMech", "found username");
589
590 const uid_t *uid = NULL;
591 found = find_if(context.begin(), context.end(), FindAuthItemByRightName("uid") );
592 if (found != context.end())
593 {
594 uid = static_cast<const uid_t *>((**found).value().data);
595 secdebug("AuthEvalMech", "found uid");
596 }
597
598 if (username.length() && uid)
599 {
600 // credential is valid because mechanism says so
601 newCredentials.insert(Credential(*uid, username, "", "", mShared));
602 }
603 } while(0);
604
605 return newCredentials;
606 }
607
608 // evaluate whether a good credential of the current session owner would authorize a right
609 OSStatus
610 RuleImpl::evaluateSessionOwner(const AuthItemRef &inRight, const Rule &inRule, const AuthItemSet &environment, const CFAbsoluteTime now, const AuthorizationToken &auth, Credential &credential, SecurityAgent::Reason &reason) const
611 {
612 // username hint is taken from the user who created the authorization, unless it's clearly ineligible
613 // @@@ we have no access to current requester uid here and the process uid is only taken when the authorization is created
614 // meaning that a process like loginwindow that drops privs later is screwed.
615
616 uid_t uid;
617 Session &session = auth.session();
618 Credential sessionCredential;
619 if (session.haveOriginatorUid()) {
620 // preflight session credential as if it were a fresh copy
621 const Credential &cred = session.originatorCredential();
622 sessionCredential = Credential(cred->uid(), cred->username(), cred->realname(), cred->groupname(), mShared/*ignored*/);
623 } else {
624 uid = auth.creatorUid();
625 Server::active().longTermActivity();
626 struct passwd *pw = getpwuid(uid);
627 if (pw != NULL) {
628 // avoid hinting a locked account
629 if ( (pw->pw_passwd == NULL) ||
630 strcmp(pw->pw_passwd, "*") ) {
631 // Check if username will authorize the request and set username to
632 // be used as a hint to the user if so
633 secdebug("AuthEvalMech", "preflight credential from current user, result follows:");
634 sessionCredential = Credential(pw->pw_uid, pw->pw_name, pw->pw_gecos, "", mShared/*ignored*/);
635 } //fi
636 endpwent();
637 }
638 }
639 OSStatus status = evaluateUserCredentialForRight(auth, inRight, inRule, environment, now, sessionCredential, true, reason);
640 if (errAuthorizationSuccess == status)
641 credential = sessionCredential;
642
643 return status;
644 }
645
646
647 OSStatus
648 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
649 {
650 if (auth.operatesAsLeastPrivileged()) {
651 if (credential->isRight() && credential->isValid() && (inRight->name() == credential->rightname()))
652 return errAuthorizationSuccess;
653 else
654 {
655 // @@@ no proper SA::Reason
656 reason = SecurityAgent::unknownReason;
657 return errAuthorizationDenied;
658 }
659 } else
660 return evaluateUserCredentialForRight(auth, inRight, inRule, environment, now, credential, false, reason);
661 }
662
663 // Return errAuthorizationSuccess if this rule allows access based on the specified credential,
664 // return errAuthorizationDenied otherwise.
665 OSStatus
666 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
667 {
668 assert(mType == kUser);
669
670 // Ideally we'd set the AGENT_HINT_RETRY_REASON hint in this method, but
671 // evaluateAuthentication() overwrites it before
672 // AgentMechanismEvaluator::run(). That's what led to passing "reason"
673 // everywhere, from RuleImpl::evaluate() on down.
674
675 // Get the username from the credential
676 const char *user = credential->username().c_str();
677
678 // If the credential is not valid or its age is more than the allowed maximum age
679 // for a credential, deny.
680 if (!credential->isValid())
681 {
682 // @@@ it could be the username, not password, was invalid
683 reason = SecurityAgent::invalidPassphrase;
684 secdebug("autheval", "credential for user %s is invalid, denying right %s", user, inRight->name());
685 return errAuthorizationDenied;
686 }
687
688 if (now - credential->creationTime() > mMaxCredentialAge)
689 {
690 // @@@ no proper SA::Reason
691 reason = SecurityAgent::unknownReason;
692 secdebug("autheval", "credential for user %s has expired, denying right %s", user, inRight->name());
693 return errAuthorizationDenied;
694 }
695
696 if (!ignoreShared && !mShared && credential->isShared())
697 {
698 // @@@ no proper SA::Reason
699 reason = SecurityAgent::unknownReason;
700 secdebug("autheval", "shared credential for user %s cannot be used, denying right %s", user, inRight->name());
701 return errAuthorizationDenied;
702 }
703
704 // A root (uid == 0) user can do anything
705 if (credential->uid() == 0)
706 {
707 secdebug("autheval", "user %s has uid 0, granting right %s", user, inRight->name());
708 return errAuthorizationSuccess;
709 }
710
711 if (mSessionOwner)
712 {
713 Session &session = auth.session();
714 if (session.haveOriginatorUid())
715 {
716 uid_t console_user = session.originatorUid();
717
718 if (credential->uid() == console_user)
719 {
720 secdebug("autheval", "user %s is session-owner(uid: %d), granting right %s", user, console_user, inRight->name());
721 return errAuthorizationSuccess;
722 }
723 // set "reason" in this case? not that a proper SA::Reason exists
724 }
725 else
726 {
727 // @@@ no proper SA::Reason
728 reason = SecurityAgent::unknownReason;
729 secdebug("autheval", "session-owner check failed.");
730 }
731 }
732
733 if (mGroupName.length())
734 {
735 const char *groupname = mGroupName.c_str();
736 Server::active().longTermActivity();
737
738 if (!groupname)
739 return errAuthorizationDenied;
740
741 do
742 {
743 uuid_t group_uuid, user_uuid;
744 int is_member;
745
746 // @@@ it'd be nice to have SA::Reason codes for the failures
747 // associated with the pre-check-membership mbr_*() functions,
748 // but userNotInGroup will do
749 if (mbr_group_name_to_uuid(groupname, group_uuid))
750 break;
751
752 if (mbr_uid_to_uuid(credential->uid(), user_uuid))
753 break;
754
755 if (mbr_check_membership(user_uuid, group_uuid, &is_member))
756 break;
757
758 if (is_member)
759 {
760 credential->setGroupname(mGroupName);
761 secdebug("autheval", "user %s is a member of group %s, granting right %s",
762 user, groupname, inRight->name());
763 return errAuthorizationSuccess;
764 }
765
766 }
767 while (0);
768
769 reason = SecurityAgent::userNotInGroup;
770 secdebug("autheval", "user %s is not a member of group %s, denying right %s",
771 user, groupname, inRight->name());
772 }
773 else if (mSessionOwner) // rule asks only if user is the session owner
774 {
775 reason = SecurityAgent::unacceptableUser;
776 }
777
778 return errAuthorizationDenied;
779 }
780
781
782
783 OSStatus
784 RuleImpl::evaluateUser(const AuthItemRef &inRight, const Rule &inRule, AuthItemSet &environmentToClient, AuthorizationFlags flags, CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, AuthorizationToken &auth, SecurityAgent::Reason &reason) const
785 {
786 // If we got here, this is a kUser type rule, let's start looking for a
787 // credential that is satisfactory
788
789 // Zeroth -- Here is an extra special saucy ugly hack to allow authorizations
790 // created by a proccess running as root to automatically get a right.
791 if (mAllowRoot && auth.creatorUid() == 0)
792 {
793 SECURITYD_AUTH_USER_ALLOWROOT(&auth);
794
795 secdebug("autheval", "creator of authorization has uid == 0 granting right %s",
796 inRight->name());
797 return errAuthorizationSuccess;
798 }
799
800 // if we're not supposed to authenticate evaluate the session-owner against the group
801 if (!mAuthenticateUser)
802 {
803 Credential hintCredential;
804 OSStatus status = evaluateSessionOwner(inRight, inRule, environmentToClient, now, auth, hintCredential, reason);
805
806 if (!status)
807 {
808 SECURITYD_AUTH_USER_ALLOWSESSIONOWNER(&auth);
809 return errAuthorizationSuccess;
810 }
811
812 return errAuthorizationDenied;
813 }
814
815 // First -- go though the credentials we either already used or obtained during this authorize operation.
816 for (CredentialSet::const_iterator it = credentials.begin(); it != credentials.end(); ++it)
817 {
818 // Passed-in user credentials are allowed for least-privileged mode
819 if (auth.operatesAsLeastPrivileged() && !(*it)->isRight() && (*it)->isValid())
820 {
821 OSStatus status = evaluateUserCredentialForRight(auth, inRight, inRule, environmentToClient, now, *it, false, reason);
822 if (errAuthorizationSuccess == status) {
823 Credential rightCredential(inRight->name(), (*it)->uid(), mShared);
824 credentials.erase(rightCredential); credentials.insert(rightCredential);
825 if (mShared)
826 credentials.insert(Credential(inRight->name(), (*it)->uid(), false));
827 return status;
828 }
829 }
830
831 // if this is least privileged, this will function differently: match credential to requested right
832 OSStatus status = evaluateCredentialForRight(auth, inRight, inRule, environmentToClient, now, *it, false, reason);
833
834 if (status != errAuthorizationDenied) {
835 // add credential to authinfo
836 auth.setCredentialInfo(*it);
837 return status;
838 }
839
840 }
841
842 // Second -- go though the credentials passed in to this authorize operation by the state management layer.
843 if (inCredentials)
844 {
845 for (CredentialSet::const_iterator it = inCredentials->begin(); it != inCredentials->end(); ++it)
846 {
847 // if this is least privileged, this will function differently: match credential to requested right
848 OSStatus status = evaluateCredentialForRight(auth, inRight, inRule, environmentToClient, now, *it, false, reason);
849
850 if (status == errAuthorizationSuccess)
851 {
852 // Add the credential we used to the output set.
853 // whack an equivalent credential, so it gets updated to a later achieved credential which must have been more stringent
854 credentials.erase(*it); credentials.insert(*it);
855 // add credential to authinfo
856 auth.setCredentialInfo(*it);
857
858 return status;
859 }
860 else if (status != errAuthorizationDenied)
861 return status;
862 }
863 }
864
865 // Finally -- We didn't find the credential in our passed in credential lists. Obtain a new credential if our flags let us do so.
866 if (!(flags & kAuthorizationFlagExtendRights))
867 return errAuthorizationDenied;
868
869 // authorizations that timeout immediately cannot be preauthorized
870 if ((flags & kAuthorizationFlagPreAuthorize) &&
871 (mMaxCredentialAge == 0.0))
872 {
873 inRight->setFlags(inRight->flags() | kAuthorizationFlagCanNotPreAuthorize);
874 return errAuthorizationSuccess;
875 }
876
877 if (!(flags & kAuthorizationFlagInteractionAllowed))
878 return errAuthorizationInteractionNotAllowed;
879
880 setAgentHints(inRight, inRule, environmentToClient, auth);
881
882 return evaluateAuthentication(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth, reason);
883 }
884
885 OSStatus
886 RuleImpl::evaluateMechanismOnly(const AuthItemRef &inRight, const Rule &inRule, AuthItemSet &environmentToClient, AuthorizationToken &auth, CredentialSet &outCredentials) const
887 {
888 uint32 tries = 0;
889 OSStatus status;
890
891 Process &cltProc = Server::process();
892 // Authorization preserves creator's UID in setuid processes
893 uid_t cltUid = (cltProc.uid() != 0) ? cltProc.uid() : auth.creatorUid();
894 secdebug("AuthEvalMech", "Mechanism invocation by process %d (UID %d)", cltProc.pid(), cltUid);
895
896 {
897 AgentMechanismEvaluator eval(cltUid, auth.session(), mEvalDef);
898 // For auditing within AuthorizationMechEval, pass the right name.
899 size_t rightNameSize = inRight->name() ? strlen(inRight->name()) : 0;
900 AuthorizationString rightName = inRight->name() ? inRight->name() : "";
901 // @@@ AuthValueRef's ctor ought to take a const void *
902 AuthValueRef rightValue(rightNameSize, const_cast<char *>(rightName));
903 AuthValueVector authValueVector;
904 authValueVector.push_back(rightValue);
905
906 do
907 {
908 setAgentHints(inRight, inRule, environmentToClient, auth);
909 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(tries), &tries));
910 environmentToClient.erase(triesHint); environmentToClient.insert(triesHint); // replace
911
912 status = eval.run(authValueVector, environmentToClient, auth);
913 if ((status == errAuthorizationSuccess) ||
914 (status == errAuthorizationCanceled)) // @@@ can only pass back sideband through context
915 {
916 secdebug("AuthEvalMech", "storing new context for authorization");
917 auth.setInfoSet(eval.context());
918 if (status == errAuthorizationSuccess)
919 {
920 // (try to) attach the authorizing UID to the least-priv cred
921 if (auth.operatesAsLeastPrivileged())
922 {
923 RightAuthenticationLogger logger(auth.creatorAuditToken(), AUE_ssauthint);
924 logger.setRight(rightName);
925
926 AuthItem *uidItem = eval.context().find(AGENT_CONTEXT_UID);
927 if (uidItem)
928 {
929 uid_t authorizedUid;
930 memcpy(&authorizedUid, uidItem->value().data, sizeof(authorizedUid));
931 secdebug("AuthEvalMech", "generating least-privilege cred for '%s' authorized by UID %u", inRight->name(), authorizedUid);
932 outCredentials.insert(Credential(rightName, authorizedUid, mShared));
933 logger.logLeastPrivilege(authorizedUid, true);
934 }
935 else // cltUid is better than nothing
936 {
937 secdebug("AuthEvalMech", "generating least-privilege cred for '%s' with process- or auth-UID %u", inRight->name(), cltUid);
938 outCredentials.insert(Credential(rightName, cltUid, mShared));
939 logger.logLeastPrivilege(cltUid, false);
940 }
941 }
942 else
943 outCredentials = makeCredentials(auth);
944 }
945 }
946
947 tries++;
948 }
949 while ((status == errAuthorizationDenied) // only if we have an expected failure we continue
950 && ((mTries == 0) // mTries == 0 means we try forever
951 || ((mTries > 0) // mTries > 0 means we try up to mTries times
952 && (tries < mTries))));
953 }
954
955 // HACK kill all hosts to free pages for low memory systems
956 // (XXX/gh there should be a #define for this right)
957 if (name() == "system.login.done")
958 {
959 // one case where we don't want to mark the agents as "busy"
960 QueryInvokeMechanism query(securityAgent, auth.session());
961 query.terminateAgent();
962 QueryInvokeMechanism query2(privilegedAuthHost, auth.session());
963 query2.terminateAgent();
964 }
965
966 return status;
967 }
968
969 OSStatus
970 RuleImpl::evaluateRules(const AuthItemRef &inRight, const Rule &inRule, AuthItemSet &environmentToClient, AuthorizationFlags flags, CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, AuthorizationToken &auth, SecurityAgent::Reason &reason) const
971 {
972 // line up the rules to try
973 if (!mRuleDef.size())
974 return errAuthorizationSuccess;
975
976 uint32_t count = 0;
977 OSStatus status = errAuthorizationSuccess;
978 vector<Rule>::const_iterator it;
979
980 for (it = mRuleDef.begin();it != mRuleDef.end(); it++)
981 {
982 // are we at k yet?
983 if ((mType == kKofN) && (count == mKofN))
984 return errAuthorizationSuccess;
985
986 // get a rule and try it
987 status = (*it)->evaluate(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth, reason);
988
989 // if status is cancel/internal error abort
990 if ((status == errAuthorizationCanceled) || (status == errAuthorizationInternal))
991 return status;
992
993 if (status != errAuthorizationSuccess)
994 {
995 // continue if we're only looking for k of n
996 if (mType == kKofN)
997 continue;
998
999 break;
1000 }
1001 else
1002 count++;
1003 }
1004
1005 return status; // return the last failure
1006 }
1007
1008
1009 OSStatus
1010 RuleImpl::evaluate(const AuthItemRef &inRight, const Rule &inRule, AuthItemSet &environmentToClient, AuthorizationFlags flags, CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, AuthorizationToken &auth, SecurityAgent::Reason &reason) const
1011 {
1012 switch (mType)
1013 {
1014 case kAllow:
1015 SECURITYD_AUTH_ALLOW(&auth, (char *)name().c_str());
1016 return errAuthorizationSuccess;
1017 case kDeny:
1018 SECURITYD_AUTH_DENY(&auth, (char *)name().c_str());
1019 return errAuthorizationDenied;
1020 case kUser:
1021 SECURITYD_AUTH_USER(&auth, (char *)name().c_str());
1022 return evaluateUser(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth, reason);
1023 case kRuleDelegation:
1024 SECURITYD_AUTH_RULES(&auth, (char *)name().c_str());
1025 return evaluateRules(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth, reason);
1026 case kKofN:
1027 SECURITYD_AUTH_KOFN(&auth, (char *)name().c_str());
1028 return evaluateRules(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth, reason);
1029 case kEvaluateMechanisms:
1030 SECURITYD_AUTH_MECHRULE(&auth, (char *)name().c_str());
1031 // if we had a SecurityAgent::Reason code for "mechanism denied,"
1032 // it would make sense to pass down "reason"
1033 return evaluateMechanismOnly(inRight, inRule, environmentToClient, auth, credentials);
1034 default:
1035 Syslog::alert("Unrecognized rule type %d", mType);
1036 MacOSError::throwMe(errAuthorizationInternal); // invalid rule
1037 }
1038 }
1039
1040 Rule::Rule() : RefPointer<RuleImpl>(new RuleImpl()) {}
1041 Rule::Rule(const string &inRightName, CFDictionaryRef cfRight, CFDictionaryRef cfRules) : RefPointer<RuleImpl>(new RuleImpl(inRightName, cfRight, cfRules)) {}
1042
1043
1044
1045 } // end namespace Authorization