]> git.saurik.com Git - apple/security.git/blob - SecurityServer/Authorization/AuthorizationRule.cpp
Security-163.tar.gz
[apple/security.git] / SecurityServer / Authorization / AuthorizationRule.cpp
1 /*
2 * AuthorizationRule.cpp
3 * Security
4 *
5 * Created by Conrad Sauerwald on Wed Mar 19 2003.
6 * Copyright (c) 2003 Apple Computer, Inc. All rights reserved.
7 *
8 */
9
10 #include "AuthorizationRule.h"
11 #include "AuthorizationTags.h"
12 #include "AuthorizationDB.h"
13 #include "AuthorizationPriv.h"
14 #include "authority.h"
15 #include "server.h"
16 #include "process.h"
17
18
19 #include <pwd.h>
20 #include <grp.h>
21 #include <unistd.h>
22
23
24 //
25 // Rule class
26 //
27 namespace Authorization {
28
29 CFStringRef RuleImpl::kUserGroupID = CFSTR(kAuthorizationRuleParameterGroup);
30 CFStringRef RuleImpl::kTimeoutID = CFSTR(kAuthorizationRuleParameterCredentialTimeout);
31 CFStringRef RuleImpl::kSharedID = CFSTR(kAuthorizationRuleParameterCredentialShared);
32 CFStringRef RuleImpl::kAllowRootID = CFSTR(kAuthorizationRuleParameterAllowRoot);
33 CFStringRef RuleImpl::kMechanismsID = CFSTR(kAuthorizationRuleParameterMechanisms);
34 CFStringRef RuleImpl::kSessionOwnerID = CFSTR(kAuthorizationRuleParameterCredentialSessionOwner);
35 CFStringRef RuleImpl::kKofNID = CFSTR(kAuthorizationRuleParameterKofN);
36 CFStringRef RuleImpl::kPromptID = CFSTR(kAuthorizationRuleParameterDefaultPrompt);
37
38 CFStringRef RuleImpl::kRuleClassID = CFSTR(kAuthorizationRuleClass);
39 CFStringRef RuleImpl::kRuleAllowID = CFSTR(kAuthorizationRuleClassAllow);
40 CFStringRef RuleImpl::kRuleDenyID = CFSTR(kAuthorizationRuleClassDeny);
41 CFStringRef RuleImpl::kRuleUserID = CFSTR(kAuthorizationRuleClassUser);
42 CFStringRef RuleImpl::kRuleDelegateID = CFSTR(kAuthorizationRightRule);
43 CFStringRef RuleImpl::kRuleMechanismsID = CFSTR(kAuthorizationRuleClassMechanisms);
44
45 string
46 RuleImpl::Attribute::getString(CFDictionaryRef config, CFStringRef key, bool required = false, char *defaultValue = NULL)
47 {
48 CFTypeRef value = CFDictionaryGetValue(config, key);
49 if (value && (CFGetTypeID(value) == CFStringGetTypeID()))
50 {
51 CFStringRef stringValue = reinterpret_cast<CFStringRef>(value);
52 char buffer[512];
53 const char *ptr = CFStringGetCStringPtr(stringValue, kCFStringEncodingUTF8);
54 if (ptr == NULL)
55 {
56 if (CFStringGetCString(stringValue, buffer, sizeof(buffer), kCFStringEncodingUTF8))
57 ptr = buffer;
58 else
59 MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule
60 }
61
62 return string(ptr);
63 }
64 else
65 if (!required)
66 return string(defaultValue);
67 else
68 MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule
69 }
70
71 double
72 RuleImpl::Attribute::getDouble(CFDictionaryRef config, CFStringRef key, bool required = false, double defaultValue = 0.0)
73 {
74 double doubleValue = 0;
75
76 CFTypeRef value = CFDictionaryGetValue(config, key);
77 if (value && (CFGetTypeID(value) == CFNumberGetTypeID()))
78 {
79 CFNumberGetValue(reinterpret_cast<CFNumberRef>(value), kCFNumberDoubleType, &doubleValue);
80 }
81 else
82 if (!required)
83 return defaultValue;
84 else
85 MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule
86
87 return doubleValue;
88 }
89
90 bool
91 RuleImpl::Attribute::getBool(CFDictionaryRef config, CFStringRef key, bool required = false, bool defaultValue = false)
92 {
93 bool boolValue = false;
94 CFTypeRef value = CFDictionaryGetValue(config, key);
95
96 if (value && (CFGetTypeID(value) == CFBooleanGetTypeID()))
97 {
98 boolValue = CFBooleanGetValue(reinterpret_cast<CFBooleanRef>(value));
99 }
100 else
101 if (!required)
102 return defaultValue;
103 else
104 MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule
105
106 return boolValue;
107 }
108
109 // add reference to string that we're modifying
110 void
111 RuleImpl::Attribute::setString(CFMutableDictionaryRef config, CFStringRef key, string &value)
112 {
113 CFStringRef cfstringValue = CFStringCreateWithCString(NULL /*allocator*/, value.c_str(), kCFStringEncodingUTF8);
114
115 if (cfstringValue)
116 {
117 CFDictionarySetValue(config, key, cfstringValue);
118 CFRelease(cfstringValue);
119 }
120 else
121 MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid attribute
122 }
123
124 void
125 RuleImpl::Attribute::setDouble(CFMutableDictionaryRef config, CFStringRef key, double value)
126 {
127 CFNumberRef doubleValue = CFNumberCreate(NULL /*allocator*/, kCFNumberDoubleType, doubleValue);
128
129 if (doubleValue)
130 {
131 CFDictionarySetValue(config, key, doubleValue);
132 CFRelease(doubleValue);
133 }
134 else
135 MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid attribute
136 }
137
138 void
139 RuleImpl::Attribute::setBool(CFMutableDictionaryRef config, CFStringRef key, bool value)
140 {
141 if (value)
142 CFDictionarySetValue(config, key, kCFBooleanTrue);
143 else
144 CFDictionarySetValue(config, key, kCFBooleanFalse);
145 }
146
147 vector<string>
148 RuleImpl::Attribute::getVector(CFDictionaryRef config, CFStringRef key, bool required = false)
149 {
150 vector<string> valueArray;
151
152 CFTypeRef value = CFDictionaryGetValue(config, key);
153 if (value && (CFGetTypeID(value) == CFArrayGetTypeID()))
154 {
155 CFArrayRef evalArray = reinterpret_cast<CFArrayRef>(value);
156
157 for (int index=0; index < CFArrayGetCount(evalArray); index++)
158 {
159 CFTypeRef arrayValue = CFArrayGetValueAtIndex(evalArray, index);
160 if (arrayValue && (CFGetTypeID(arrayValue) == CFStringGetTypeID()))
161 {
162 CFStringRef stringValue = reinterpret_cast<CFStringRef>(arrayValue);
163 char buffer[512];
164 const char *ptr = CFStringGetCStringPtr(stringValue, kCFStringEncodingUTF8);
165 if (ptr == NULL)
166 {
167 if (CFStringGetCString(stringValue, buffer, sizeof(buffer), kCFStringEncodingUTF8))
168 ptr = buffer;
169 else
170 MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule
171 }
172 valueArray.push_back(string(ptr));
173 }
174 }
175 }
176 else
177 if (required)
178 MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule
179
180 return valueArray;
181 }
182
183
184 bool RuleImpl::Attribute::getLocalizedPrompts(CFDictionaryRef config, map<string,string> &localizedPrompts)
185 {
186 CFIndex numberOfPrompts = 0;
187 CFDictionaryRef promptsDict;
188 if (CFDictionaryContainsKey(config, kPromptID))
189 {
190 promptsDict = reinterpret_cast<CFDictionaryRef>(CFDictionaryGetValue(config, kPromptID));
191 if (promptsDict && (CFGetTypeID(promptsDict) == CFDictionaryGetTypeID()))
192 numberOfPrompts = CFDictionaryGetCount(promptsDict);
193 }
194 if (numberOfPrompts == 0)
195 return false;
196
197 const void *keys[numberOfPrompts+1];
198 const void *values[numberOfPrompts+1];
199 CFDictionaryGetKeysAndValues(promptsDict, &keys[0], &values[0]);
200
201 while (numberOfPrompts-- > 0)
202 {
203 CFStringRef keyRef = reinterpret_cast<CFStringRef>(keys[numberOfPrompts]);
204 CFStringRef valueRef = reinterpret_cast<CFStringRef>(values[numberOfPrompts]);
205 if (!keyRef || (CFGetTypeID(keyRef) != CFStringGetTypeID()))
206 continue;
207 if (!valueRef || (CFGetTypeID(valueRef) != CFStringGetTypeID()))
208 continue;
209 string key = cfString(keyRef);
210 string value = cfString(valueRef);
211 localizedPrompts["description"+key] = value;
212 }
213
214 return true;
215 }
216
217
218 // default rule
219 RuleImpl::RuleImpl() :
220 mType(kUser), mGroupName("admin"), mMaxCredentialAge(300.0), mShared(true), mAllowRoot(false), mSessionOwner(false), mTries(0)
221 {
222 // XXX/cs read default descriptions from somewhere
223 // @@@ Default rule is shared admin group with 5 minute timeout
224 }
225
226 // return rule built from rule definition; throw if invalid.
227 RuleImpl::RuleImpl(const string &inRightName, CFDictionaryRef cfRight, CFDictionaryRef cfRules) : mRightName(inRightName)
228 {
229 // @@@ make sure cfRight is non mutable and never used that way
230
231 if (CFGetTypeID(cfRight) != CFDictionaryGetTypeID())
232 MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule
233
234 mTries = 0;
235
236 string classTag = Attribute::getString(cfRight, kRuleClassID, false, "");
237
238 if (classTag.length())
239 {
240 if (classTag == kAuthorizationRuleClassAllow)
241 {
242 secdebug("authrule", "%s : rule allow", inRightName.c_str());
243 mType = kAllow;
244 }
245 else if (classTag == kAuthorizationRuleClassDeny)
246 {
247 secdebug("authrule", "%s : rule deny", inRightName.c_str());
248 mType = kDeny;
249 }
250 else if (classTag == kAuthorizationRuleClassUser)
251 {
252 mType = kUser;
253 mGroupName = Attribute::getString(cfRight, kUserGroupID);
254 // grab other user-in-group attributes
255 mMaxCredentialAge = Attribute::getDouble(cfRight, kTimeoutID, false, DBL_MAX);
256 mShared = Attribute::getBool(cfRight, kSharedID);
257 mAllowRoot = Attribute::getBool(cfRight, kAllowRootID);
258 mSessionOwner = Attribute::getBool(cfRight, kSessionOwnerID);
259 // authorization tags can have eval now too
260 mEvalDef = Attribute::getVector(cfRight, kMechanismsID);
261 mTries = 3;
262
263 secdebug("authrule", "%s : rule user in group \"%s\" timeout %g%s%s",
264 inRightName.c_str(),
265 mGroupName.c_str(), mMaxCredentialAge, mShared ? " shared" : "",
266 mAllowRoot ? " allow-root" : "");
267
268 }
269 else if (classTag == kAuthorizationRuleClassMechanisms)
270 {
271 secdebug("authrule", "%s : rule evaluate mechanisms", inRightName.c_str());
272 mType = kEvaluateMechanisms;
273 // mechanisms to evaluate
274 mEvalDef = Attribute::getVector(cfRight, kMechanismsID, true);
275 }
276 else if (classTag == kAuthorizationRightRule)
277 {
278 assert(cfRules); // this had better not be a rule
279 secdebug("authrule", "%s : rule delegate rule", inRightName.c_str());
280 mType = kRuleDelegation;
281
282 // string or
283 string ruleDefString = Attribute::getString(cfRight, kRuleDelegateID, false, "");
284 if (ruleDefString.length())
285 {
286 CFStringRef ruleDefRef = makeCFString(ruleDefString);
287 CFDictionaryRef cfRuleDef = reinterpret_cast<CFDictionaryRef>(CFDictionaryGetValue(cfRules, ruleDefRef));
288 if (ruleDefRef)
289 CFRelease(ruleDefRef);
290 if (!cfRuleDef || CFGetTypeID(cfRuleDef) != CFDictionaryGetTypeID())
291 MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule
292 mRuleDef.push_back(Rule(ruleDefString, cfRuleDef, NULL));
293 }
294 else // array
295 {
296 vector<string> ruleDef = Attribute::getVector(cfRight, kRuleDelegateID, true);
297 for (vector<string>::const_iterator it = ruleDef.begin(); it != ruleDef.end(); it++)
298 {
299 CFStringRef ruleNameRef = makeCFString(*it);
300 CFDictionaryRef cfRuleDef = reinterpret_cast<CFDictionaryRef>(CFDictionaryGetValue(cfRules, ruleNameRef));
301 if (ruleNameRef)
302 CFRelease(ruleNameRef);
303 if (!cfRuleDef || (CFGetTypeID(cfRuleDef) != CFDictionaryGetTypeID()))
304 MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule
305 mRuleDef.push_back(Rule(*it, cfRuleDef, NULL));
306 }
307 }
308
309 mKofN = int(Attribute::getDouble(cfRight, kKofNID, false, 0.0));
310 if (mKofN)
311 mType = kKofN;
312
313 }
314 else
315 {
316 secdebug("authrule", "%s : rule class unknown %s.", inRightName.c_str(), classTag.c_str());
317 MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule
318 }
319 }
320 else
321 {
322 // no class tag means, this is the abbreviated specification from the API
323 // it _must_ have a definition for "rule" which will be used as a delegate
324 // it may have a comment (not extracted here)
325 // it may have a default prompt, or a whole dictionary of languages (not extracted here)
326 assert(cfRules);
327 mType = kRuleDelegation;
328 string ruleName = Attribute::getString(cfRight, kRuleDelegateID, true);
329 secdebug("authrule", "%s : rule delegate rule (1): %s", inRightName.c_str(), ruleName.c_str());
330 CFStringRef ruleNameRef = makeCFString(ruleName);
331 CFDictionaryRef cfRuleDef = reinterpret_cast<CFDictionaryRef>(CFDictionaryGetValue(cfRules, ruleNameRef));
332 if (ruleNameRef)
333 CFRelease(ruleNameRef);
334 if (!cfRuleDef || CFGetTypeID(cfRuleDef) != CFDictionaryGetTypeID())
335 MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule
336 mRuleDef.push_back(Rule(ruleName, cfRuleDef, NULL));
337 }
338
339 Attribute::getLocalizedPrompts(cfRight, mLocalizedPrompts);
340 }
341
342 /*
343 RuleImpl::~Rule()
344 {
345 }
346 */
347
348 void
349 RuleImpl::setAgentHints(const AuthItemRef &inRight, const Rule &inTopLevelRule, AuthItemSet &environmentToClient, AuthorizationToken &auth) const
350 {
351 string authorizeString(inRight->name());
352 environmentToClient.insert(AuthItemRef(AGENT_HINT_AUTHORIZE_RIGHT, AuthValueOverlay(authorizeString)));
353
354 // XXX/cs pid/uid/client should only be added when we're ready to call the agent
355 pid_t cPid = Server::connection().process.pid();
356 environmentToClient.insert(AuthItemRef(AGENT_HINT_CLIENT_PID, AuthValueOverlay(sizeof(pid_t), &cPid)));
357
358 uid_t cUid = auth.creatorUid();
359 environmentToClient.insert(AuthItemRef(AGENT_HINT_CLIENT_UID, AuthValueOverlay(sizeof(uid_t), &cUid)));
360
361 pid_t creatorPid = auth.creatorPid();
362 environmentToClient.insert(AuthItemRef(AGENT_HINT_CREATOR_PID, AuthValueOverlay(sizeof(pid_t), &creatorPid)));
363
364 {
365 CodeSigning::OSXCode *osxcode = auth.creatorCode();
366 if (!osxcode)
367 MacOSError::throwMe(errAuthorizationDenied);
368
369 string encodedBundle = osxcode->encode();
370 char bundleType = (encodedBundle.c_str())[0]; // yay, no accessor
371 string bundlePath = osxcode->canonicalPath();
372
373 environmentToClient.insert(AuthItemRef(AGENT_HINT_CLIENT_TYPE, AuthValueOverlay(sizeof(bundleType), &bundleType)));
374 environmentToClient.insert(AuthItemRef(AGENT_HINT_CLIENT_PATH, AuthValueOverlay(bundlePath)));
375 }
376
377 map<string,string> defaultPrompts = inTopLevelRule->localizedPrompts();
378
379 if (defaultPrompts.empty())
380 defaultPrompts = localizedPrompts();
381
382 if (!defaultPrompts.empty())
383 {
384 map<string,string>::const_iterator it;
385 for (it = defaultPrompts.begin(); it != defaultPrompts.end(); it++)
386 {
387 const string &key = it->first;
388 const string &value = it->second;
389 environmentToClient.insert(AuthItemRef(key.c_str(), AuthValueOverlay(value)));
390 }
391 }
392
393 // add rulename as a hint
394 string ruleName = name();
395 environmentToClient.insert(AuthItemRef(AGENT_HINT_AUTHORIZE_RULE, AuthValueOverlay(ruleName)));
396 }
397
398 string
399 RuleImpl::agentNameForAuth(const AuthorizationToken &auth) const
400 {
401 uint8_t hash[20];
402 AuthorizationBlob authBlob = auth.handle();
403 CssmData hashedData = CssmData::wrap(&hash, sizeof(hash));
404 CssmData data = CssmData::wrap(&authBlob, sizeof(authBlob));
405 CssmClient::Digest ctx(Server::csp(), CSSM_ALGID_SHA1);
406 try {
407 ctx.digest(data, hashedData);
408 }
409 catch (CssmError &e)
410 {
411 secdebug("auth", "digesting authref failed (%lu)", e.cssmError());
412 return string("SecurityAgentMechanism");
413 }
414
415 uint8_t *point = static_cast<uint8_t*>(hashedData.data());
416 for (uint8_t i=0; i < hashedData.length(); point++, i++)
417 {
418 uint8 value = (*point % 62) + '0';
419 if (value > '9') value += 7;
420 if (value > 'Z') value += 6;
421 *point = value;
422 }
423 return string(static_cast<char *>(hashedData.data()), hashedData.length());
424 }
425
426 OSStatus
427 RuleImpl::evaluateMechanism(const AuthItemRef &inRight, const AuthItemSet &environment, AuthorizationToken &auth, CredentialSet &outCredentials) const
428 {
429 string agentName = agentNameForAuth(auth);
430
431 // @@@ configuration does not support arguments
432 AuthValueVector arguments;
433 // XXX/cs Move this up - we shouldn't know how to retrieve the ingoing context
434 AuthItemSet context = auth.infoSet();
435 AuthItemSet hints = environment;
436
437 AuthorizationResult result = kAuthorizationResultAllow;
438 vector<string>::const_iterator currentMechanism = mEvalDef.begin();
439
440 while ( (result == kAuthorizationResultAllow) &&
441 (currentMechanism != mEvalDef.end()) ) // iterate mechanisms
442 {
443 string::size_type extPlugin = currentMechanism->find(':');
444 if (extPlugin != string::npos)
445 {
446 // no whitespace removal
447 string pluginIn(currentMechanism->substr(0, extPlugin));
448 string mechanismIn(currentMechanism->substr(extPlugin + 1));
449 secdebug("SSevalMech", "external mech %s:%s", pluginIn.c_str(), mechanismIn.c_str());
450
451 bool mechExecOk = false; // successfully ran a mechanism
452
453 Process &cltProc = Server::active().connection().process;
454 // Authorization preserves creator's UID in setuid processes
455 uid_t cltUid = (cltProc.uid() != 0) ? cltProc.uid() : auth.creatorUid();
456 secdebug("SSevalMech", "Mechanism invocation by process %d (UID %d)", cltProc.pid(), cltUid);
457 QueryInvokeMechanism client(cltUid, auth, agentName.c_str());
458 try
459 {
460 mechExecOk = client(pluginIn, mechanismIn, arguments, hints, context, &result);
461 }
462 catch (...) {
463 secdebug("SSevalMech", "exception from mech eval or client death");
464 // various server problems, but only if it really failed
465 if (mechExecOk != true)
466 result = kAuthorizationResultUndefined;
467 }
468
469 secdebug("SSevalMech", "evaluate(plugin: %s, mechanism: %s) %s, result: %lu.", pluginIn.c_str(), mechanismIn.c_str(), (mechExecOk == true) ? "succeeded" : "failed", result);
470 }
471 else
472 {
473 // internal mechanisms - no glue
474 if (*currentMechanism == "authinternal")
475 {
476 secdebug("SSevalMech", "evaluate authinternal");
477 result = kAuthorizationResultDeny;
478 do {
479 AuthItemSet::iterator found = find_if(context.begin(), context.end(), FindAuthItemByRightName(kAuthorizationEnvironmentUsername) );
480 if (found == context.end())
481 break;
482 string username(static_cast<const char *>((*found)->value().data), (*found)->value().length);
483 secdebug("SSevalMech", "found username");
484 found = find_if(context.begin(), context.end(), FindAuthItemByRightName(kAuthorizationEnvironmentPassword) );
485 if (found == context.end())
486 break;
487 string password(static_cast<const char *>((*found)->value().data), (*found)->value().length);
488 secdebug("SSevalMech", "found password");
489 Credential newCredential(username, password, true); // create a new shared credential
490
491 if (newCredential->isValid())
492 Syslog::info("authinternal authenticated user %s (uid %lu) for right %s.", newCredential->username().c_str(), newCredential->uid(), inRight->name());
493 else
494 // we can't be sure that the user actually exists so inhibit logging of uid
495 Syslog::error("authinternal failed to authenticate user %s for right %s.", newCredential->username().c_str(), inRight->name());
496
497 if (newCredential->isValid())
498 {
499 outCredentials.clear(); // only keep last one
500 secdebug("SSevalMech", "inserting new credential");
501 outCredentials.insert(newCredential);
502 result = kAuthorizationResultAllow;
503 } else
504 result = kAuthorizationResultDeny;
505 } while (0);
506 }
507 else
508 if (*currentMechanism == "push_hints_to_context")
509 {
510 secdebug("SSevalMech", "evaluate push_hints_to_context");
511 mTries = 1; // XXX/cs this should be set in authorization config
512 result = kAuthorizationResultAllow; // snarfcredential doesn't block evaluation, ever, it may restart
513 // create out context from input hints, no merge
514 // @@@ global copy template not being invoked...
515 context = hints;
516 }
517 else
518 if (*currentMechanism == "switch_to_user")
519 {
520 Process &cltProc = Server::active().connection().process;
521 // Authorization preserves creator's UID in setuid processes
522 uid_t cltUid = (cltProc.uid() != 0) ? cltProc.uid() : auth.creatorUid();
523 secdebug("SSevalMech", "terminating agent at request of process %d (UID %d)\n", cltProc.pid(), cltUid);
524 QueryInvokeMechanism client(cltUid, auth, agentName.c_str());
525
526 try {
527 client.terminateAgent();
528 } catch (...) {
529 // Not our agent
530 }
531 result = kAuthorizationResultAllow;
532 }
533 }
534
535 // we own outHints and outContext
536 switch(result)
537 {
538 case kAuthorizationResultAllow:
539 secdebug("SSevalMech", "result allow");
540 currentMechanism++;
541 break;
542 case kAuthorizationResultDeny:
543 secdebug("SSevalMech", "result deny");
544 break;
545 case kAuthorizationResultUndefined:
546 secdebug("SSevalMech", "result undefined");
547 break; // abort evaluation
548 case kAuthorizationResultUserCanceled:
549 secdebug("SSevalMech", "result canceled");
550 break; // stop evaluation, return some sideband
551 default:
552 break; // abort evaluation
553 }
554 }
555
556 // End of evaluation, if last step produced meaningful data, incorporate
557 if ((result == kAuthorizationResultAllow) ||
558 (result == kAuthorizationResultUserCanceled)) // @@@ can only pass back sideband through context
559 {
560 secdebug("SSevalMech", "storing new context for authorization");
561 auth.setInfoSet(context);
562 }
563
564 switch(result)
565 {
566 case kAuthorizationResultDeny:
567 return errAuthorizationDenied;
568 case kAuthorizationResultUserCanceled:
569 return errAuthorizationCanceled;
570 case kAuthorizationResultAllow:
571 return errAuthorizationSuccess;
572 default:
573 return errAuthorizationInternal;
574 }
575 }
576
577
578
579 OSStatus
580 RuleImpl::evaluateAuthorization(const AuthItemRef &inRight, const Rule &inRule,
581 AuthItemSet &environmentToClient,
582 AuthorizationFlags flags, CFAbsoluteTime now,
583 const CredentialSet *inCredentials,
584 CredentialSet &credentials, AuthorizationToken &auth) const
585 {
586 OSStatus status = errAuthorizationDenied;
587
588 string usernamehint;
589 evaluateSessionOwner(inRight, inRule, environmentToClient, now, auth, usernamehint);
590 if (usernamehint.length())
591 environmentToClient.insert(AuthItemRef(AGENT_HINT_SUGGESTED_USER, AuthValueOverlay(usernamehint)));
592
593 if ((mType == kUser) && (mGroupName.length()))
594 environmentToClient.insert(AuthItemRef(AGENT_HINT_REQUIRE_USER_IN_GROUP, AuthValueOverlay(mGroupName)));
595
596 uint32 tries;
597 SecurityAgent::Reason reason = SecurityAgent::noReason;
598
599 for (tries = 0; tries < mTries; tries++)
600 {
601 AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
602 environmentToClient.erase(retryHint); environmentToClient.insert(retryHint); // replace
603 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(tries), &tries));
604 environmentToClient.erase(triesHint); environmentToClient.insert(triesHint); // replace
605
606 status = evaluateMechanism(inRight, environmentToClient, auth, credentials);
607
608 // successfully ran mechanisms to obtain credential
609 if (status == errAuthorizationSuccess)
610 {
611 // deny is the default
612 status = errAuthorizationDenied;
613
614 // fetch context and construct a credential to be tested
615 AuthItemSet inContext = auth.infoSet();
616 CredentialSet newCredentials = makeCredentials(inContext);
617
618 for (CredentialSet::const_iterator it = newCredentials.begin(); it != newCredentials.end(); ++it)
619 {
620 const Credential& newCredential = *it;
621
622 // @@@ we log the uid a process was running under when it created the authref, which is misleading in the case of loginwindow
623 if (newCredential->isValid())
624 Syslog::info("uid %lu succeeded authenticating as user %s (uid %lu) for right %s.", auth.creatorUid(), newCredential->username().c_str(), newCredential->uid(), inRight->name());
625 else
626 // we can't be sure that the user actually exists so inhibit logging of uid
627 Syslog::error("uid %lu failed to authenticate as user %s for right %s.", auth.creatorUid(), newCredential->username().c_str(), inRight->name());
628
629 if (!newCredential->isValid())
630 {
631 reason = SecurityAgent::invalidPassphrase; //invalidPassphrase;
632 continue;
633 }
634
635 // verify that this credential authorizes right
636 status = evaluateCredentialForRight(inRight, inRule, environmentToClient, now, newCredential, true);
637
638 if (status == errAuthorizationSuccess)
639 {
640 // whack an equivalent credential, so it gets updated to a later achieved credential which must have been more stringent
641 credentials.erase(newCredential); credentials.insert(newCredential);
642 secdebug("SSevalMech", "added valid credential for user %s", newCredential->username().c_str());
643 status = errAuthorizationSuccess;
644 break;
645 }
646 else
647 reason = SecurityAgent::userNotInGroup; //unacceptableUser; // userNotInGroup
648 }
649
650 if (status == errAuthorizationSuccess)
651 break;
652 }
653 else
654 if ((status == errAuthorizationCanceled) ||
655 (status == errAuthorizationInternal))
656 break;
657 }
658
659 // If we fell out of the loop because of too many tries, notify user
660 if (tries == mTries)
661 {
662 reason = SecurityAgent::tooManyTries;
663 AuthItemRef retryHint (AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason));
664 environmentToClient.erase(retryHint); environmentToClient.insert(retryHint); // replace
665 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(tries), &tries));
666 environmentToClient.erase(triesHint); environmentToClient.insert(triesHint); // replace
667 evaluateMechanism(inRight, environmentToClient, auth, credentials);
668 }
669
670 Process &cltProc = Server::active().connection().process;
671 // Authorization preserves creator's UID in setuid processes
672 uid_t cltUid = (cltProc.uid() != 0) ? cltProc.uid() : auth.creatorUid();
673 secdebug("SSevalMech", "terminating agent at request of process %d (UID %d)\n", cltProc.pid(), cltUid);
674 string agentName = agentNameForAuth(auth);
675 QueryInvokeMechanism client(cltUid, auth, agentName.c_str());
676
677 try {
678 client.terminateAgent();
679 } catch (...) {
680 // Not our agent
681 }
682
683 return status;
684 }
685
686 // create externally verified credentials on the basis of
687 // mechanism-provided information
688 CredentialSet
689 RuleImpl::makeCredentials(const AuthItemSet &context) const
690 {
691 CredentialSet newCredentials;
692
693 do {
694 AuthItemSet::const_iterator found = find_if(context.begin(), context.end(), FindAuthItemByRightName(kAuthorizationEnvironmentUsername) );
695 if (found == context.end())
696 break;
697 string username = (**found).stringValue();
698 secdebug("SSevalMech", "found username");
699
700 const uid_t *uid = NULL;
701 found = find_if(context.begin(), context.end(), FindAuthItemByRightName("uid") );
702 if (found != context.end())
703 {
704 uid = static_cast<const uid_t *>((**found).value().data);
705 secdebug("SSevalMech", "found uid");
706 }
707
708 const gid_t *gid = NULL;
709 found = find_if(context.begin(), context.end(), FindAuthItemByRightName("gid") );
710 if (found != context.end())
711 {
712 gid = static_cast<const gid_t *>((**found).value().data);
713 secdebug("SSevalMech", "found gid");
714 }
715
716 if (username.length() && uid && gid)
717 {
718 // credential is valid because mechanism says so
719 newCredentials.insert(Credential(username, *uid, *gid, mShared));
720 }
721 else
722 {
723 found = find_if(context.begin(), context.end(), FindAuthItemByRightName(kAuthorizationEnvironmentPassword) );
724 if (found != context.end())
725 {
726 secdebug("SSevalMech", "found password");
727 string password = (**found).stringValue();
728 secdebug("SSevalMech", "falling back on username/password credential if valid");
729 newCredentials.insert(Credential(username, password, mShared));
730 }
731 }
732 } while(0);
733
734 return newCredentials;
735 }
736
737 // evaluate whether a good credential of the current session owner would authorize a right
738 OSStatus
739 RuleImpl::evaluateSessionOwner(const AuthItemRef &inRight, const Rule &inRule,
740 const AuthItemSet &environment,
741 const CFAbsoluteTime now,
742 const AuthorizationToken &auth,
743 string& usernamehint) const
744 {
745 // username hint is taken from the user who created the authorization, unless it's clearly ineligible
746 OSStatus status = noErr;
747 // @@@ we have no access to current requester uid here and the process uid is only taken when the authorization is created
748 // meaning that a process like loginwindow that drops privs later is screwed.
749 uid_t uid = auth.creatorUid();
750
751 Server::active().longTermActivity();
752 struct passwd *pw = getpwuid(uid);
753 if (pw != NULL)
754 {
755 // avoid hinting a locked account (ie. root)
756 if ( (pw->pw_passwd == NULL) ||
757 strcmp(pw->pw_passwd, "*") ) {
758 // Check if username will authorize the request and set username to
759 // be used as a hint to the user if so
760 status = evaluateCredentialForRight(inRight, inRule, environment, now, Credential(pw->pw_name, pw->pw_uid, pw->pw_gid, mShared), true);
761
762 if (status == errAuthorizationSuccess)
763 usernamehint = pw->pw_name;
764 } //fi
765 endpwent();
766 }
767 return status;
768 }
769
770
771
772 // Return errAuthorizationSuccess if this rule allows access based on the specified credential,
773 // return errAuthorizationDenied otherwise.
774 OSStatus
775 RuleImpl::evaluateCredentialForRight(const AuthItemRef &inRight, const Rule &inRule, const AuthItemSet &environment, CFAbsoluteTime now, const Credential &credential, bool ignoreShared) const
776 {
777 assert(mType == kUser);
778
779 // Get the username from the credential
780 const char *user = credential->username().c_str();
781
782 // If the credential is not valid or it's age is more than the allowed maximum age
783 // for a credential, deny.
784 if (!credential->isValid())
785 {
786 secdebug("autheval", "credential for user %s is invalid, denying right %s", user, inRight->name());
787 return errAuthorizationDenied;
788 }
789
790 if (now - credential->creationTime() > mMaxCredentialAge)
791 {
792 secdebug("autheval", "credential for user %s has expired, denying right %s", user, inRight->name());
793 return errAuthorizationDenied;
794 }
795
796 if (!ignoreShared && !mShared && credential->isShared())
797 {
798 secdebug("autheval", "shared credential for user %s cannot be used, denying right %s", user, inRight->name());
799 return errAuthorizationDenied;
800 }
801
802 // A root (uid == 0) user can do anything
803 if (credential->uid() == 0)
804 {
805 secdebug("autheval", "user %s has uid 0, granting right %s", user, inRight->name());
806 return errAuthorizationSuccess;
807 }
808
809 // XXX/cs replace with remembered session-owner once that functionality is added to SecurityServer
810 if (mSessionOwner)
811 {
812 uid_t console_user;
813 struct stat console_stat;
814 if (!lstat("/dev/console", &console_stat))
815 {
816 console_user = console_stat.st_uid;
817 if (credential->uid() == console_user)
818 {
819 secdebug("autheval", "user %s is session-owner(uid: %d), granting right %s", user, console_user, inRight->name());
820 return errAuthorizationSuccess;
821 }
822 }
823 else
824 secdebug("autheval", "session-owner check failed.");
825 }
826
827 if (mGroupName.length())
828 {
829 const char *groupname = mGroupName.c_str();
830 Server::active().longTermActivity();
831 struct group *gr = getgrnam(groupname);
832 if (!gr)
833 return errAuthorizationDenied;
834
835 // Is this the default group of this user?
836 // PR-2875126 <grp.h> declares gr_gid int, as opposed to advertised (getgrent(3)) gid_t
837 // When this is fixed this warning should go away.
838 if (credential->gid() == gr->gr_gid)
839 {
840 secdebug("autheval", "user %s has group %s(%d) as default group, granting right %s",
841 user, groupname, gr->gr_gid, inRight->name());
842 endgrent();
843 return errAuthorizationSuccess;
844 }
845
846 for (char **group = gr->gr_mem; *group; ++group)
847 {
848 if (!strcmp(*group, user))
849 {
850 secdebug("autheval", "user %s is a member of group %s, granting right %s",
851 user, groupname, inRight->name());
852 endgrent();
853 return errAuthorizationSuccess;
854 }
855 }
856
857 secdebug("autheval", "user %s is not a member of group %s, denying right %s",
858 user, groupname, inRight->name());
859 endgrent();
860 }
861
862 return errAuthorizationDenied;
863 }
864
865 OSStatus
866 RuleImpl::evaluateUser(const AuthItemRef &inRight, const Rule &inRule,
867 AuthItemSet &environmentToClient, AuthorizationFlags flags,
868 CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials,
869 AuthorizationToken &auth) const
870 {
871 // If we got here, this is a kUser type rule, let's start looking for a
872 // credential that is satisfactory
873
874 // Zeroth -- Here is an extra special saucy ugly hack to allow authorizations
875 // created by a proccess running as root to automatically get a right.
876 if (mAllowRoot && auth.creatorUid() == 0)
877 {
878 secdebug("autheval", "creator of authorization has uid == 0 granting right %s",
879 inRight->name());
880 return errAuthorizationSuccess;
881 }
882
883 // if this is a "is-admin" rule check that and return
884 // XXX/cs add way to specify is-admin class of rule: if (mNoVerify)
885 if (name() == kAuthorizationRuleIsAdmin)
886 {
887 string username;
888 if (!evaluateSessionOwner(inRight, inRule, environmentToClient, now, auth, username))
889 return errAuthorizationSuccess;
890 }
891
892 // First -- go though the credentials we either already used or obtained during this authorize operation.
893 for (CredentialSet::const_iterator it = credentials.begin(); it != credentials.end(); ++it)
894 {
895 OSStatus status = evaluateCredentialForRight(inRight, inRule, environmentToClient, now, *it, true);
896 if (status != errAuthorizationDenied)
897 {
898 // add credential to authinfo
899 auth.setCredentialInfo(*it);
900 return status;
901 }
902 }
903
904 // Second -- go though the credentials passed in to this authorize operation by the state management layer.
905 if (inCredentials)
906 {
907 for (CredentialSet::const_iterator it = inCredentials->begin(); it != inCredentials->end(); ++it)
908 {
909 OSStatus status = evaluateCredentialForRight(inRight, inRule, environmentToClient, now, *it, false);
910 if (status == errAuthorizationSuccess)
911 {
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);
917
918 return status;
919 }
920 else if (status != errAuthorizationDenied)
921 return status;
922 }
923 }
924
925 // Finally -- We didn't find the credential in our passed in credential lists. Obtain a new credential if
926 // our flags let us do so.
927 if (!(flags & kAuthorizationFlagExtendRights))
928 return errAuthorizationDenied;
929
930 // authorizations that timeout immediately cannot be preauthorized
931 if ((flags & kAuthorizationFlagPreAuthorize) &&
932 (mMaxCredentialAge == 0.0))
933 {
934 inRight->setFlags(inRight->flags() | kAuthorizationFlagCanNotPreAuthorize);
935 return errAuthorizationSuccess;
936 }
937
938 if (!(flags & kAuthorizationFlagInteractionAllowed))
939 return errAuthorizationInteractionNotAllowed;
940
941 setAgentHints(inRight, inRule, environmentToClient, auth);
942
943 // If a different evaluation is prescribed,
944 // we'll run that and validate the credentials from there
945 // we fall back on a default configuration
946 if (mEvalDef.size() == 0)
947 return evaluateAuthorizationOld(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth);
948 else
949 return evaluateAuthorization(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth);
950 }
951
952 // XXX/cs insert a mechanism that let's the agent live (keep-alive) only in loginwindow's case
953 OSStatus
954 RuleImpl::evaluateMechanismOnly(const AuthItemRef &inRight, const Rule &inRule, AuthItemSet &environmentToClient, AuthorizationToken &auth, CredentialSet &outCredentials) const
955 {
956 uint32 tries = 0;
957 OSStatus status;
958
959 do
960 {
961 setAgentHints(inRight, inRule, environmentToClient, auth);
962 AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(tries), &tries));
963 environmentToClient.erase(triesHint); environmentToClient.insert(triesHint); // replace
964
965 status = evaluateMechanism(inRight, environmentToClient, auth, outCredentials);
966 tries++;
967 }
968 while ((status == errAuthorizationDenied) // only if we have an expected failure we continue
969 && ((mTries == 0) // mTries == 0 means we try forever
970 || ((mTries > 0) // mTries > 0 means we try up to mTries times
971 && (tries < mTries))));
972
973 if (name() != "system.login.console")
974 {
975 // terminate agent
976 string agentName = agentNameForAuth(auth);
977 Process &cltProc = Server::active().connection().process;
978 // Authorization preserves creator's UID in setuid processes
979 uid_t cltUid = (cltProc.uid() != 0) ? cltProc.uid() : auth.creatorUid();
980 secdebug("SSevalMech", "terminating agent at request of process %d (UID %d)\n", cltProc.pid(), cltUid);
981
982 QueryInvokeMechanism client(cltUid, auth, agentName.c_str());
983
984 try
985 {
986 client.terminateAgent();
987 } catch (...) {
988 // Not our agent
989 }
990 }
991 return status;
992 }
993
994 OSStatus
995 RuleImpl::evaluateRules(const AuthItemRef &inRight, const Rule &inRule,
996 AuthItemSet &environmentToClient, AuthorizationFlags flags,
997 CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials,
998 AuthorizationToken &auth) const
999 {
1000 // line up the rules to try
1001 if (!mRuleDef.size())
1002 return errAuthorizationSuccess;
1003
1004 uint32_t count = 0;
1005 OSStatus status = errAuthorizationSuccess;
1006 vector<Rule>::const_iterator it;
1007
1008 for (it = mRuleDef.begin();it != mRuleDef.end(); it++)
1009 {
1010 // are we at k yet?
1011 if ((mType == kKofN) && (count == mKofN))
1012 return errAuthorizationSuccess;
1013
1014 // get a rule and try it
1015 status = (*it)->evaluate(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth);
1016
1017 // if status is cancel/internal error abort
1018 if ((status == errAuthorizationCanceled) || (status == errAuthorizationInternal))
1019 return status;
1020
1021 if (status != errAuthorizationSuccess)
1022 {
1023 // continue if we're only looking for k of n
1024 if (mType == kKofN)
1025 continue;
1026
1027 break;
1028 }
1029 else
1030 count++;
1031 }
1032
1033 return status; // return the last failure
1034 }
1035
1036
1037 OSStatus
1038 RuleImpl::evaluate(const AuthItemRef &inRight, const Rule &inRule,
1039 AuthItemSet &environmentToClient, AuthorizationFlags flags,
1040 CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials,
1041 AuthorizationToken &auth) const
1042 {
1043 switch (mType)
1044 {
1045 case kAllow:
1046 secdebug("autheval", "rule is always allow");
1047 return errAuthorizationSuccess;
1048 case kDeny:
1049 secdebug("autheval", "rule is always deny");
1050 return errAuthorizationDenied;
1051 case kUser:
1052 secdebug("autheval", "rule is user");
1053 return evaluateUser(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth);
1054 case kRuleDelegation:
1055 secdebug("autheval", "rule evaluates rules");
1056 return evaluateRules(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth);
1057 case kKofN:
1058 secdebug("autheval", "rule evaluates k-of-n rules");
1059 return evaluateRules(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth);
1060 case kEvaluateMechanisms:
1061 secdebug("autheval", "rule evaluates mechanisms");
1062 return evaluateMechanismOnly(inRight, inRule, environmentToClient, auth, credentials);
1063 default:
1064 MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule
1065 }
1066 }
1067
1068
1069
1070
1071 // This is slated to be removed when the new auth panel is fixed up
1072 OSStatus
1073 RuleImpl::evaluateAuthorizationOld(const AuthItemRef &inRight, const Rule &inRule,
1074 AuthItemSet &environmentToClient,
1075 AuthorizationFlags flags, CFAbsoluteTime now,
1076 const CredentialSet *inCredentials,
1077 CredentialSet &credentials, AuthorizationToken &auth) const
1078 {
1079 Process &cltProc = Server::active().connection().process;
1080 // Authorization preserves creator's UID in setuid processes
1081 uid_t cltUid = (cltProc.uid() != 0) ? cltProc.uid() : auth.creatorUid();
1082 secdebug("autheval", "Auth query from process %d (UID %d)", cltProc.pid(), cltUid);
1083 QueryAuthorizeByGroup query(cltUid, auth);
1084
1085 string usernamehint;
1086
1087 evaluateSessionOwner(inRight, inRule, environmentToClient, now, auth, usernamehint);
1088
1089 Credential newCredential;
1090 // @@@ Keep the default reason the same, so the agent only gets userNotInGroup or invalidPassphrase
1091 SecurityAgent::Reason reason = SecurityAgent::userNotInGroup;
1092 // @@@ Hardcoded 3 tries to avoid infinite loops.
1093 for (uint32_t tryCount = 0; tryCount < mTries; ++tryCount)
1094 {
1095 // Obtain a new credential. Anything but success is considered an error.
1096 OSStatus status = obtainCredential(query, inRight, environmentToClient, usernamehint.c_str(), newCredential, reason);
1097 if (status)
1098 return status;
1099
1100 // Now we have successfully obtained a credential we need to make sure it authorizes the requested right
1101 if (!newCredential->isValid())
1102 reason = SecurityAgent::invalidPassphrase;
1103 else {
1104 status = evaluateCredentialForRight(inRight, inRule, environmentToClient, now, newCredential, true);
1105 if (status == errAuthorizationSuccess)
1106 {
1107 // Add the new credential we obtained to the output set.
1108 // whack an equivalent credential, so it gets updated to a later achieved credential which must have been more stringent
1109 credentials.erase(newCredential); credentials.insert(newCredential);
1110 query.done();
1111
1112 // add credential to authinfo
1113 auth.setCredentialInfo(newCredential);
1114
1115 return errAuthorizationSuccess;
1116 }
1117 else if (status != errAuthorizationDenied)
1118 return status;
1119 }
1120 reason = SecurityAgent::userNotInGroup;
1121 }
1122 query.cancel(SecurityAgent::tooManyTries);
1123 return errAuthorizationDenied;
1124 }
1125
1126 OSStatus
1127 RuleImpl::obtainCredential(QueryAuthorizeByGroup &query, const AuthItemRef &inRight,
1128 AuthItemSet &environmentToClient, const char *usernameHint, Credential &outCredential, SecurityAgent::Reason reason) const
1129 {
1130 char nameBuffer[SecurityAgent::maxUsernameLength];
1131 char passphraseBuffer[SecurityAgent::maxPassphraseLength];
1132 OSStatus status = errAuthorizationDenied;
1133
1134 try {
1135 if (query(mGroupName.c_str(), usernameHint, nameBuffer, passphraseBuffer, reason))
1136 status = noErr;
1137 } catch (const CssmCommonError &err) {
1138 status = err.osStatus();
1139 } catch (...) {
1140 status = errAuthorizationInternal;
1141 }
1142 if (status == CSSM_ERRCODE_USER_CANCELED)
1143 {
1144 secdebug("auth", "canceled obtaining credential for user in group %s", mGroupName.c_str());
1145 return errAuthorizationCanceled;
1146 }
1147 if (status == CSSM_ERRCODE_NO_USER_INTERACTION)
1148 {
1149 secdebug("auth", "user interaction not possible obtaining credential for user in group %s", mGroupName.c_str());
1150 return errAuthorizationInteractionNotAllowed;
1151 }
1152
1153 if (status != noErr)
1154 {
1155 secdebug("auth", "failed obtaining credential for user in group %s", mGroupName.c_str());
1156 return status;
1157 }
1158
1159 secdebug("auth", "obtained credential for user %s", nameBuffer);
1160 string username(nameBuffer);
1161 string password(passphraseBuffer);
1162 outCredential = Credential(username, password, mShared);
1163 return errAuthorizationSuccess;
1164 }
1165
1166
1167 Rule::Rule() : RefPointer<RuleImpl>(new RuleImpl()) {}
1168 Rule::Rule(const string &inRightName, CFDictionaryRef cfRight, CFDictionaryRef cfRules) : RefPointer<RuleImpl>(new RuleImpl(inRightName, cfRight, cfRules)) {}
1169
1170
1171 } // end namespace Authorization