]> git.saurik.com Git - apple/security.git/blob - SecurityServer/Authorization/AuthorizationEngine.cpp
Security-54.tar.gz
[apple/security.git] / SecurityServer / Authorization / AuthorizationEngine.cpp
1 /*
2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
3 *
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
8 * using this file.
9 *
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
16 */
17
18
19 /*
20 * AuthorizationEngine.cpp
21 * Authorization
22 *
23 * Created by Michael Brouwer on Thu Oct 12 2000.
24 * Copyright (c) 2000 Apple Computer Inc. All rights reserved.
25 *
26 */
27 #include "AuthorizationEngine.h"
28 #include <Security/AuthorizationWalkers.h>
29
30 #include "server.h"
31 #include "authority.h"
32
33 #include <Security/AuthorizationTags.h>
34 #include <Security/logging.h>
35 #include <Security/cfutilities.h>
36 #include <Security/debugging.h>
37 #include "session.h"
38
39 #include <CoreFoundation/CFData.h>
40 #include <CoreFoundation/CFNumber.h>
41 #include <CoreFoundation/CFPropertyList.h>
42
43 #include <errno.h>
44 #include <fcntl.h>
45 #include <float.h>
46 #include <unistd.h>
47 #include <grp.h>
48 #include <pwd.h>
49 #include <Security/checkpw.h>
50
51 // checkpw() that uses provided struct passwd
52 extern "C"
53 {
54 int checkpw_internal( const char* userName, const char* password, const struct passwd *pw );
55 }
56
57 namespace Authorization {
58
59
60 //
61 // Errors to be thrown
62 //
63 Error::Error(int err) : error(err)
64 {
65 }
66
67 const char *Error::what() const throw()
68 { return "Authorization error"; }
69
70 CSSM_RETURN Error::cssmError() const throw()
71 { return error; } // @@@ eventually...
72
73 OSStatus Error::osStatus() const throw()
74 { return error; }
75
76 void Error::throwMe(int err) { throw Error(err); }
77
78
79 //
80 // CredentialImpl class
81 //
82
83 // only for testing whether this credential is usable
84 CredentialImpl::CredentialImpl(const string &username, const uid_t uid, const gid_t gid, bool shared) :
85 mUsername(username), mShared(shared), mUid(uid), mGid(gid), mCreationTime(CFAbsoluteTimeGetCurrent()), mValid(true)
86 {
87 }
88
89 // credential with validity based on username/password combination.
90 CredentialImpl::CredentialImpl(const string &username, const string &password, bool shared) :
91 mShared(shared), mCreationTime(CFAbsoluteTimeGetCurrent()), mValid(false)
92 {
93 // try short name first
94 const char *user = username.c_str();
95 struct passwd *pw = getpwnam(user);
96
97 do {
98
99 if (!pw)
100 {
101 debug("autheval", "user %s not found, creating invalid credential", user);
102 break;
103 }
104
105 const char *passwd = password.c_str();
106 int checkpw_status = checkpw_internal(user, passwd, pw);
107
108 if (checkpw_status != CHECKPW_SUCCESS)
109 {
110 debug("autheval", "checkpw() for user %s failed with error %d, creating invalid credential", user, checkpw_status);
111 break;
112 }
113
114 debug("autheval", "checkpw() for user %s succeeded, creating%s credential",
115 user, mShared ? " shared" : "");
116
117 mUsername = string ( pw->pw_name );
118 mUid = pw->pw_uid;
119 mGid = pw->pw_gid;
120 mValid = true;
121
122 endpwent();
123 }
124 while (0);
125 }
126
127
128 CredentialImpl::~CredentialImpl()
129 {
130 }
131
132 bool
133 CredentialImpl::operator < (const CredentialImpl &other) const
134 {
135 if (!mShared && other.mShared)
136 return true;
137 if (!other.mShared && mShared)
138 return false;
139
140 return mUsername < other.mUsername;
141 }
142
143 // Returns true if this CredentialImpl should be shared.
144 bool
145 CredentialImpl::isShared() const
146 {
147 return mShared;
148 }
149
150 // Merge with other
151 void
152 CredentialImpl::merge(const CredentialImpl &other)
153 {
154 assert(mUsername == other.mUsername);
155
156 if (other.mValid && (!mValid || mCreationTime < other.mCreationTime))
157 {
158 mCreationTime = other.mCreationTime;
159 mUid = other.mUid;
160 mGid = other.mGid;
161 mValid = true;
162 }
163 }
164
165 // The time at which this credential was obtained.
166 CFAbsoluteTime
167 CredentialImpl::creationTime() const
168 {
169 return mCreationTime;
170 }
171
172 // Return true iff this credential is valid.
173 bool
174 CredentialImpl::isValid() const
175 {
176 return mValid;
177 }
178
179 void
180 CredentialImpl::invalidate()
181 {
182 mValid = false;
183 }
184
185 //
186 // Credential class
187 //
188 Credential::Credential() :
189 RefPointer<CredentialImpl>(NULL)
190 {
191 }
192
193 Credential::Credential(CredentialImpl *impl) :
194 RefPointer<CredentialImpl>(impl)
195 {
196 }
197
198 Credential::Credential(const string &username, const uid_t uid, const gid_t gid, bool shared) :
199 RefPointer<CredentialImpl>(new CredentialImpl(username, uid, gid, shared))
200 {
201 }
202
203 Credential::Credential(const string &username, const string &password, bool shared) :
204 RefPointer<CredentialImpl>(new CredentialImpl(username, password, shared))
205 {
206 }
207
208 Credential::~Credential()
209 {
210 }
211
212 bool
213 Credential::operator < (const Credential &other) const
214 {
215 if (!*this)
216 return other;
217
218 if (!other)
219 return false;
220
221 return (**this) < (*other);
222 }
223
224
225 //
226 // Rule class
227 //
228 CFStringRef Rule::kUserInGroupID = CFSTR("group");
229 CFStringRef Rule::kTimeoutID = CFSTR("timeout");
230 CFStringRef Rule::kSharedID = CFSTR("shared");
231 CFStringRef Rule::kAllowRootID = CFSTR("allow-root");
232 CFStringRef Rule::kDenyID = CFSTR("deny");
233 CFStringRef Rule::kAllowID = CFSTR("allow");
234 CFStringRef Rule::kEvalMechID = CFSTR("eval");
235
236
237 Rule::Rule() :
238 mType(kUserInGroup), mGroupName("admin"), mMaxCredentialAge(300.0), mShared(true), mAllowRoot(false)
239 {
240 // @@@ Default rule is shared admin group with 5 minute timeout
241 }
242
243 Rule::Rule(CFTypeRef cfRule)
244 {
245 // @@@ This code is ugly. Serves me right for using CF.
246 if (CFGetTypeID(cfRule) == CFStringGetTypeID())
247 {
248 CFStringRef tag = reinterpret_cast<CFStringRef>(cfRule);
249 if (CFEqual(kAllowID, tag))
250 {
251 debug("authrule", "rule always allow");
252 mType = kAllow;
253 }
254 else if (CFEqual(kDenyID, tag))
255 {
256 debug("authrule", "rule always deny");
257 mType = kDeny;
258 }
259 else
260 Error::throwMe();
261 }
262 else if (CFGetTypeID(cfRule) == CFDictionaryGetTypeID())
263 {
264 CFDictionaryRef dict = reinterpret_cast<CFDictionaryRef>(cfRule);
265 CFTypeRef groupTag = CFDictionaryGetValue(dict, kUserInGroupID);
266
267 // Probably a user in group rule
268 if (groupTag)
269 {
270 if (CFGetTypeID(groupTag) != CFStringGetTypeID())
271 Error::throwMe();
272
273 mType = kUserInGroup;
274
275 CFStringRef group = reinterpret_cast<CFStringRef>(groupTag);
276 char buffer[512];
277 const char *ptr = CFStringGetCStringPtr(group, kCFStringEncodingUTF8);
278 if (ptr == NULL)
279 {
280 if (CFStringGetCString(group, buffer, 512, kCFStringEncodingUTF8))
281 ptr = buffer;
282 else
283 Error::throwMe();
284 }
285
286 mGroupName = string(ptr);
287
288 mMaxCredentialAge = DBL_MAX;
289 CFTypeRef timeoutTag = CFDictionaryGetValue(dict, kTimeoutID);
290 if (timeoutTag)
291 {
292 if (CFGetTypeID(timeoutTag) != CFNumberGetTypeID())
293 Error::throwMe();
294 CFNumberGetValue(reinterpret_cast<CFNumberRef>(timeoutTag), kCFNumberDoubleType, &mMaxCredentialAge);
295 }
296
297 CFTypeRef sharedTag = CFDictionaryGetValue(dict, kSharedID);
298 mShared = false;
299 if (sharedTag)
300 {
301 if (CFGetTypeID(sharedTag) != CFBooleanGetTypeID())
302 Error::throwMe();
303 mShared = CFBooleanGetValue(reinterpret_cast<CFBooleanRef>(sharedTag));
304 }
305
306 CFTypeRef allowRootTag = CFDictionaryGetValue(dict, kAllowRootID);
307 mAllowRoot = false;
308 if (allowRootTag)
309 {
310 if (CFGetTypeID(allowRootTag) != CFBooleanGetTypeID())
311 Error::throwMe();
312 mAllowRoot = CFBooleanGetValue(reinterpret_cast<CFBooleanRef>(allowRootTag));
313 }
314 debug("authrule", "rule user in group \"%s\" timeout %g%s%s",
315 mGroupName.c_str(), mMaxCredentialAge, mShared ? " shared" : "",
316 mAllowRoot ? " allow-root" : "");
317 }
318 else
319 {
320 CFTypeRef mechTag = CFDictionaryGetValue(dict, kEvalMechID);
321 if (mechTag)
322 {
323 if (CFGetTypeID(mechTag) != CFStringGetTypeID())
324 Error::throwMe();
325
326 mType = kEvalMech;
327
328 CFStringRef eval = reinterpret_cast<CFStringRef>(mechTag);
329 char buffer[512];
330 const char *ptr = CFStringGetCStringPtr(eval, kCFStringEncodingUTF8);
331 if (ptr == NULL)
332 {
333 if (CFStringGetCString(eval, buffer, 512, kCFStringEncodingUTF8))
334 ptr = buffer;
335 else
336 Error::throwMe();
337 }
338 mEvalDef = string(ptr);
339 }
340 else
341 Error::throwMe();
342 }
343
344 }
345 }
346
347 Rule::Rule(const Rule &other) :
348 mType(other.mType),
349 mGroupName(other.mGroupName),
350 mMaxCredentialAge(other.mMaxCredentialAge),
351 mShared(other.mShared),
352 mAllowRoot(other.mAllowRoot),
353 mEvalDef(other.mEvalDef)
354 {
355 }
356
357 Rule &
358 Rule::operator = (const Rule &other)
359 {
360 mType = other.mType;
361 mGroupName = other.mGroupName;
362 mMaxCredentialAge = other.mMaxCredentialAge;
363 mShared = other.mShared;
364 mAllowRoot = other.mAllowRoot;
365 mEvalDef = other.mEvalDef;
366 return *this;
367 }
368
369 Rule::~Rule()
370 {
371 }
372
373
374 OSStatus
375 Rule::evaluateMechanism(const AuthorizationEnvironment *environment, AuthorizationToken &auth, CredentialSet &outCredentials)
376 {
377 assert(mType == kEvalMech);
378
379 if (mEvalDef.length() == 0) // no definition
380 return kAuthorizationResultAllow;
381
382 // mechanisms are split by commas
383 vector<string> mechanismNames;
384 {
385 string::size_type cursor = 0, comma = 0;
386 string token = "";
387
388 while (cursor < mEvalDef.length())
389 {
390 comma = mEvalDef.find(',', cursor);
391 if (comma == string::npos)
392 comma = mEvalDef.length();
393
394 token = mEvalDef.substr(cursor, comma - cursor);
395
396 // skip empty tokens
397 if (token.length() > 0)
398 mechanismNames.push_back(token);
399
400 cursor = comma + 1;
401 }
402 }
403
404 // @@@ configuration does not support arguments
405 const AuthorizationValueVector arguments = { 0, NULL };
406 MutableAuthItemSet *context = NULL;
407 AuthItemSet *hints = NULL;
408 AuthorizationItemSet *outHints = NULL, *outContext = NULL;
409 bool userInteraction = true;
410
411 CssmAllocator& alloc = CssmAllocator::standard();
412
413 AuthorizationResult result = kAuthorizationResultAllow;
414 vector<string>::iterator currentMechanism = mechanismNames.begin();
415
416 while ( (result == kAuthorizationResultAllow) &&
417 (currentMechanism != mechanismNames.end()) ) // iterate mechanisms
418 {
419 AuthorizationItemSet *inHints, *inContext;
420
421 // release after invocation, ignored for first pass
422 if (outContext)
423 {
424 inContext = outContext;
425 debug("SSevalMech", "set up context %p as input", inContext);
426 delete context;
427 context = new MutableAuthItemSet(inContext);
428 }
429 else
430 {
431 inContext = &auth.infoSet(); // returns deep copy
432 debug("SSevalMech", "set up stored context %p as input", inContext);
433 delete context;
434 context = new MutableAuthItemSet(inContext);
435 }
436
437 if (outHints)
438 {
439 inHints = outHints;
440 debug("SSevalMech", "set up hints %p as input", inHints);
441 delete hints;
442 hints = new AuthItemSet(outHints);
443 }
444 else
445 {
446 inHints = NULL;
447 debug("SSevalMech", "set up environment hints %p as input", environment);
448 delete hints;
449 hints = new AuthItemSet(environment);
450 }
451
452 string::size_type extPlugin = currentMechanism->find(':');
453 if (extPlugin != string::npos)
454 {
455 // no whitespace removal
456 string pluginIn(currentMechanism->substr(0, extPlugin));
457 string mechanismIn(currentMechanism->substr(extPlugin + 1));
458 debug("SSevalMech", "external mech %s:%s", pluginIn.c_str(), mechanismIn.c_str());
459
460 bool mechExecOk = false; // successfully ran a mechanism
461
462 try
463 {
464 Process &cltProc = Server::active().connection().process;
465 // Authorization preserves creator's UID in setuid processes
466 uid_t cltUid = (cltProc.uid() != 0) ? cltProc.uid() : auth.creatorUid();
467 debug("SSevalMech", "Mechanism invocation by process %d (UID %d)", cltProc.pid(), cltUid);
468 QueryInvokeMechanism client(cltUid, auth);
469
470 mechExecOk = client(pluginIn, mechanismIn, &arguments, *hints, *context, &result, outHints, outContext);
471 debug("SSevalMech", "new context %p, new hints %p", outContext, outHints);
472 }
473 catch (...) {
474 debug("SSevalMech", "exception from mech eval or client death");
475 // various server problems, but only if it really failed
476 if (mechExecOk != true)
477 result = kAuthorizationResultUndefined;
478 }
479
480 debug("SSevalMech", "evaluate(plugin: %s, mechanism: %s) %s, result: %lu.", pluginIn.c_str(), mechanismIn.c_str(), (mechExecOk == true) ? "succeeded" : "failed", result);
481 debug("SSevalMech", "mech eval okay");
482
483 // Things worked and there is new context, so get rid of old
484 if (mechExecOk)
485 {
486 if (inContext)
487 {
488 debug("SSevalMech", "release input context %p", inContext);
489 alloc.free(inContext);
490 }
491 if (inHints)
492 {
493 debug("SSevalMech", "release input hints %p", inHints);
494 alloc.free(inHints);
495 }
496 }
497 else
498 {
499 // reset previous context and hints
500 debug("SSevalMech", "resetting previous input context %p and hints %p", inContext, inHints);
501 outContext = inContext;
502 outHints = inHints;
503 }
504 }
505 else
506 {
507 // internal mechanisms - no glue
508 if (*currentMechanism == "authinternal")
509 {
510 debug("SSevalMech", "evaluate authinternal");
511 result = kAuthorizationResultDeny;
512 do {
513 MutableAuthItemSet::iterator found = find_if(context->begin(), context->end(), FindAuthItemByRightName(kAuthorizationEnvironmentUsername) );
514 if (found == context->end())
515 break;
516 string username(static_cast<const char *>(found->argument()), found->argumentLength());
517 debug("SSevalMech", "found username");
518 found = find_if(context->begin(), context->end(), FindAuthItemByRightName(kAuthorizationEnvironmentPassword) );
519 if (found == context->end())
520 break;
521 string password(static_cast<const char *>(found->argument()), found->argumentLength());
522 debug("SSevalMech", "found password");
523 Credential newCredential(username, password, true); // create a new shared credential
524 if (newCredential->isValid())
525 {
526 outCredentials.clear(); // only keep last one
527 debug("SSevalMech", "inserting new credential");
528 outCredentials.insert(newCredential);
529 result = kAuthorizationResultAllow;
530 } else
531 result = kAuthorizationResultDeny;
532 } while (0);
533 }
534 else
535 if (*currentMechanism == "push_hints_to_context")
536 {
537 debug("SSevalMech", "evaluate push_hints_to_context");
538 userInteraction = false; // we can't talk to the user
539 result = kAuthorizationResultAllow; // snarfcredential doesn't block evaluation, ever, it may restart
540 // clean up current context
541 if (inContext)
542 {
543 debug("SSevalMech", "release input context %p", inContext);
544 alloc.free(inContext);
545 }
546 // create out context from input hints, no merge
547 // @@@ global copy template not being invoked...
548 outContext = Copier<AuthorizationItemSet>(*hints).keep();
549 }
550 else
551 if (*currentMechanism == "switch_to_user")
552 {
553 try {
554 Process &cltProc = Server::active().connection().process;
555 // Authorization preserves creator's UID in setuid processes
556 uid_t cltUid = (cltProc.uid() != 0) ? cltProc.uid() : auth.creatorUid();
557 debug("SSevalMech", "terminating agent at request of process %d (UID %d)\n", cltProc.pid(), cltUid);
558 QueryTerminateAgent client(cltUid, auth);
559 client();
560 } catch (...) {
561 // Not our agent
562 }
563 result = kAuthorizationResultAllow;
564 }
565
566
567
568 }
569
570
571 // we own outHints and outContext
572 switch(result)
573 {
574 case kAuthorizationResultAllow:
575 debug("SSevalMech", "result allow");
576 currentMechanism++;
577 break;
578 case kAuthorizationResultDeny:
579 debug("SSevalMech", "result deny");
580 if (inContext)
581 {
582 debug("SSevalMech", "abort eval, release input context %p", inContext);
583 alloc.free(inContext);
584 }
585 if (inHints)
586 {
587 debug("SSevalMech", "abort eval, release input hints %p", inHints);
588 alloc.free(inHints);
589 }
590 outContext = outHints = NULL; // making sure things get reset
591 if (userInteraction)
592 {
593 currentMechanism = mechanismNames.begin();
594 result = kAuthorizationResultAllow; // stay in loop
595 }
596 break;
597 case kAuthorizationResultUndefined:
598 debug("SSevalMech", "result undefined");
599 break; // abort evaluation
600 case kAuthorizationResultUserCanceled:
601 debug("SSevalMech", "result canceled");
602 break; // stop evaluation, return some sideband
603 default:
604 break; // abort evaluation
605 }
606 }
607
608 // End of evaluation, if last step produced meaningful data, incorporate
609 if ((result == kAuthorizationResultAllow) ||
610 (result == kAuthorizationResultUserCanceled)) // @@@ can only pass back sideband through context
611 {
612 debug("SSevalMech", "make new context %p available", outContext);
613 auth.setInfoSet(*outContext);
614 outContext = NULL;
615 }
616
617 // clean up last outContext and outHints, if any
618 if (outContext)
619 {
620 debug("SSevalMech", "release output context %p", outContext);
621 alloc.free(outContext);
622 }
623 if (outHints)
624 {
625 debug("SSevalMech", "release output hints %p", outHints);
626 alloc.free(outHints);
627 }
628
629 // deny on user cancel
630 switch(result)
631 {
632 case kAuthorizationResultUndefined:
633 return errAuthorizationDenied;
634 case kAuthorizationResultDeny:
635 return errAuthorizationDenied;
636 default:
637 return errAuthorizationSuccess; // @@@ cancel should return cancelled
638 }
639 }
640
641 OSStatus
642 Rule::evaluate(const Right &inRight,
643 const AuthorizationEnvironment *environment, AuthorizationFlags flags,
644 CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials,
645 AuthorizationToken &auth)
646 {
647 switch (mType)
648 {
649 case kAllow:
650 debug("autheval", "rule is always allow");
651 return errAuthorizationSuccess;
652 case kDeny:
653 debug("autheval", "rule is always deny");
654 return errAuthorizationDenied;
655 case kUserInGroup:
656 debug("autheval", "rule is user in group");
657 break;
658 case kEvalMech:
659 debug("autheval", "rule evalutes mechanisms");
660 return evaluateMechanism(environment, auth, credentials);
661 default:
662 Error::throwMe();
663 }
664
665 // If we got here, this is a kUserInGroup type rule, let's start looking for a
666 // credential that is satisfactory
667
668 // Zeroth -- Here is an extra special saucy ugly hack to allow authorizations
669 // created by a proccess running as root to automatically get a right.
670 if (mAllowRoot && auth.creatorUid() == 0)
671 {
672 debug("autheval", "creator of authorization has uid == 0 granting right %s",
673 inRight.rightName());
674 return errAuthorizationSuccess;
675 }
676
677 // First -- go though the credentials we either already used or obtained during this authorize operation.
678 for (CredentialSet::const_iterator it = credentials.begin(); it != credentials.end(); ++it)
679 {
680 OSStatus status = evaluate(inRight, environment, now, *it, true);
681 if (status != errAuthorizationDenied)
682 {
683 // add credential to authinfo
684 auth.setCredentialInfo(*it);
685 return status;
686 }
687 }
688
689 // Second -- go though the credentials passed in to this authorize operation by the state management layer.
690 if (inCredentials)
691 {
692 for (CredentialSet::const_iterator it = inCredentials->begin(); it != inCredentials->end(); ++it)
693 {
694 OSStatus status = evaluate(inRight, environment, now, *it, false);
695 if (status == errAuthorizationSuccess)
696 {
697 // Add the credential we used to the output set.
698 // @@@ Deal with potential credential merges.
699 credentials.insert(*it);
700 // add credential to authinfo
701 auth.setCredentialInfo(*it);
702
703 return status;
704 }
705 else if (status != errAuthorizationDenied)
706 return status;
707 }
708 }
709
710 // Finally -- We didn't find the credential in our passed in credential lists. Obtain a new credential if
711 // our flags let us do so.
712 if (!(flags & kAuthorizationFlagExtendRights))
713 return errAuthorizationDenied;
714
715 if (!(flags & kAuthorizationFlagInteractionAllowed))
716 return errAuthorizationInteractionNotAllowed;
717
718 Process &cltProc = Server::active().connection().process;
719 // Authorization preserves creator's UID in setuid processes
720 uid_t cltUid = (cltProc.uid() != 0) ? cltProc.uid() : auth.creatorUid();
721 IFDEBUG(debug("autheval", "Auth query from process %d (UID %d)", cltProc.pid(), cltUid));
722 QueryAuthorizeByGroup query(cltUid, auth);
723
724 string usernamehint;
725 // username hint is taken from the user who created the authorization, unless it's clearly ineligible
726 if (uid_t uid = auth.creatorUid()) {
727 struct passwd *pw = getpwuid(uid);
728 if (pw != NULL)
729 {
730 // avoid hinting a locked account (ie. root)
731 if ( (pw->pw_passwd == NULL) ||
732 strcmp(pw->pw_passwd, "*") ) {
733 // Check if username will authorize the request and set username to
734 // be used as a hint to the user if so
735 if (evaluate(inRight, environment, now, Credential(pw->pw_name, pw->pw_uid, pw->pw_gid, mShared), true) == errAuthorizationSuccess) {
736
737 // user long name as hint
738 usernamehint = string( pw->pw_gecos );
739 #if 0
740 // minus other gecos crud
741 size_t comma = usernamehint.find(',');
742 if (comma)
743 usernamehint = usernamehint.substr(0, comma);
744 // or fallback to short username
745 #endif
746 if (usernamehint.size() == 0)
747 usernamehint = string( pw->pw_name );
748 } //fi
749 } //fi
750 endpwent();
751 }
752 }
753
754 Credential newCredential;
755 // @@@ Keep the default reason the same, so the agent only gets userNotInGroup or invalidPassphrase
756 SecurityAgent::Reason reason = SecurityAgent::userNotInGroup;
757 // @@@ Hardcoded 3 tries to avoid infinite loops.
758 for (int tryCount = 0; tryCount < 3; ++tryCount)
759 {
760 // Obtain a new credential. Anything but success is considered an error.
761 OSStatus status = obtainCredential(query, inRight, environment, usernamehint.c_str(), newCredential, reason);
762 if (status)
763 return status;
764
765 // Now we have successfully obtained a credential we need to make sure it authorizes the requested right
766 if (!newCredential->isValid())
767 reason = SecurityAgent::invalidPassphrase;
768 else {
769 status = evaluate(inRight, environment, now, newCredential, true);
770 if (status == errAuthorizationSuccess)
771 {
772 // Add the new credential we obtained to the output set.
773 // @@@ Deal with potential credential merges.
774 credentials.insert(newCredential);
775 query.done();
776
777 // add credential to authinfo
778 auth.setCredentialInfo(newCredential);
779
780 return errAuthorizationSuccess;
781 }
782 else if (status != errAuthorizationDenied)
783 return status;
784 reason = SecurityAgent::userNotInGroup;
785 }
786 }
787
788 query.cancel(SecurityAgent::tooManyTries);
789 return errAuthorizationDenied;
790 }
791
792 // Return errAuthorizationSuccess if this rule allows access based on the specified credential,
793 // return errAuthorizationDenied otherwise.
794 OSStatus
795 Rule::evaluate(const Right &inRight, const AuthorizationEnvironment *environment, CFAbsoluteTime now,
796 const Credential &credential, bool ignoreShared)
797 {
798 assert(mType == kUserInGroup);
799
800 // Get the username from the credential
801 const char *user = credential->username().c_str();
802
803 // If the credential is not valid or it's age is more than the allowed maximum age
804 // for a credential, deny.
805 if (!credential->isValid())
806 {
807 debug("autheval", "credential for user %s is invalid, denying right %s", user, inRight.rightName());
808 return errAuthorizationDenied;
809 }
810
811 if (now - credential->creationTime() > mMaxCredentialAge)
812 {
813 debug("autheval", "credential for user %s has expired, denying right %s", user, inRight.rightName());
814 return errAuthorizationDenied;
815 }
816
817 if (!ignoreShared && !mShared && credential->isShared())
818 {
819 debug("autheval", "shared credential for user %s cannot be used, denying right %s", user, inRight.rightName());
820 return errAuthorizationDenied;
821 }
822
823 // A root (uid == 0) user can do anything
824 if (credential->uid() == 0)
825 {
826 debug("autheval", "user %s has uid 0, granting right %s", user, inRight.rightName());
827 return errAuthorizationSuccess;
828 }
829
830 const char *groupname = mGroupName.c_str();
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 debug("autheval", "user %s has group %s(%d) as default group, granting right %s",
841 user, groupname, gr->gr_gid, inRight.rightName());
842 endgrent();
843 return errAuthorizationSuccess;
844 }
845
846 for (char **group = gr->gr_mem; *group; ++group)
847 {
848 if (!strcmp(*group, user))
849 {
850 debug("autheval", "user %s is a member of group %s, granting right %s",
851 user, groupname, inRight.rightName());
852 endgrent();
853 return errAuthorizationSuccess;
854 }
855 }
856
857 debug("autheval", "user %s is not a member of group %s, denying right %s",
858 user, groupname, inRight.rightName());
859 endgrent();
860 return errAuthorizationDenied;
861 }
862
863 OSStatus
864 Rule::obtainCredential(QueryAuthorizeByGroup &query, const Right &inRight,
865 const AuthorizationEnvironment *environment, const char *usernameHint, Credential &outCredential, SecurityAgent::Reason reason)
866 {
867 char nameBuffer[SecurityAgent::maxUsernameLength];
868 char passphraseBuffer[SecurityAgent::maxPassphraseLength];
869 OSStatus status = errAuthorizationDenied;
870
871 try {
872 if (query(mGroupName.c_str(), usernameHint, nameBuffer, passphraseBuffer, reason))
873 status = noErr;
874 } catch (const CssmCommonError &err) {
875 status = err.osStatus();
876 } catch (...) {
877 status = errAuthorizationInternal;
878 }
879 if (status == CSSM_ERRCODE_USER_CANCELED)
880 {
881 debug("auth", "canceled obtaining credential for user in group %s", mGroupName.c_str());
882 return errAuthorizationCanceled;
883 }
884 if (status == CSSM_ERRCODE_NO_USER_INTERACTION)
885 {
886 debug("auth", "user interaction not possible obtaining credential for user in group %s", mGroupName.c_str());
887 return errAuthorizationInteractionNotAllowed;
888 }
889
890 if (status != noErr)
891 {
892 debug("auth", "failed obtaining credential for user in group %s", mGroupName.c_str());
893 return status;
894 }
895
896 debug("auth", "obtained credential for user %s", nameBuffer);
897
898 string username(nameBuffer);
899 string password(passphraseBuffer);
900 outCredential = Credential(username, password, mShared);
901 return errAuthorizationSuccess;
902 }
903
904
905 //
906 // Engine class
907 //
908 Engine::Engine(const char *configFile) :
909 mLastChecked(DBL_MIN)
910 {
911 mRulesFileName = new char[strlen(configFile) + 1];
912 strcpy(mRulesFileName, configFile);
913 memset(&mRulesFileMtimespec, 0, sizeof(mRulesFileMtimespec));
914 }
915
916 Engine::~Engine()
917 {
918 delete[] mRulesFileName;
919 }
920
921 void
922 Engine::updateRules(CFAbsoluteTime now)
923 {
924 StLock<Mutex> _(mLock);
925 if (mRules.empty())
926 readRules();
927 else
928 {
929 // Don't do anything if we checked the timestamp less than 5 seconds ago
930 if (mLastChecked > now - 5.0)
931 return;
932
933 struct stat st;
934 if (stat(mRulesFileName, &st))
935 {
936 Syslog::error("Stating rules file \"%s\": %s", mRulesFileName, strerror(errno));
937 /* @@@ No rules file found, use defaults: admin group for everything. */
938 //UnixError::throwMe(errno);
939 }
940 else
941 {
942 // @@@ Make sure this is the right way to compare 2 struct timespec thingies
943 // Technically we should check st_dev and st_ino as well since if either of those change
944 // we are looking at a different file too.
945 if (memcmp(&st.st_mtimespec, &mRulesFileMtimespec, sizeof(mRulesFileMtimespec)))
946 readRules();
947 }
948 }
949
950 mLastChecked = now;
951 }
952
953 void
954 Engine::readRules()
955 {
956 // Make an entry in the mRules map that matches every right to the default Rule.
957 mRules.clear();
958 mRules.insert(RuleMap::value_type(string(), Rule()));
959
960 int fd = open(mRulesFileName, O_RDONLY, 0);
961 if (fd == -1)
962 {
963 Syslog::error("Opening rules file \"%s\": %s", mRulesFileName, strerror(errno));
964 return;
965 }
966
967 try
968 {
969 struct stat st;
970 if (fstat(fd, &st))
971 UnixError::throwMe(errno);
972
973 mRulesFileMtimespec = st.st_mtimespec;
974
975 off_t fileSize = st.st_size;
976
977 CFRef<CFMutableDataRef> xmlData(CFDataCreateMutable(NULL, fileSize));
978 CFDataSetLength(xmlData, fileSize);
979 void *buffer = CFDataGetMutableBytePtr(xmlData);
980 size_t bytesRead = read(fd, buffer, fileSize);
981 if (bytesRead != fileSize)
982 {
983 if (bytesRead == static_cast<size_t>(-1))
984 {
985 Syslog::error("Reading rules file \"%s\": %s", mRulesFileName, strerror(errno));
986 return;
987 }
988
989 Syslog::error("Could only read %ul out of %ul bytes from rules file \"%s\"",
990 bytesRead, fileSize, mRulesFileName);
991 return;
992 }
993
994 CFStringRef errorString;
995 CFRef<CFDictionaryRef> newRoot(reinterpret_cast<CFDictionaryRef>
996 (CFPropertyListCreateFromXMLData(NULL, xmlData, kCFPropertyListImmutable, &errorString)));
997 if (!newRoot)
998 {
999 char buffer[512];
1000 const char *error = CFStringGetCStringPtr(errorString, kCFStringEncodingUTF8);
1001 if (error == NULL)
1002 {
1003 if (CFStringGetCString(errorString, buffer, 512, kCFStringEncodingUTF8))
1004 error = buffer;
1005 }
1006
1007 Syslog::error("Parsing rules file \"%s\": %s", mRulesFileName, error);
1008 return;
1009 }
1010
1011 if (CFGetTypeID(newRoot) != CFDictionaryGetTypeID())
1012 {
1013 Syslog::error("Rules file \"%s\": is not a dictionary", mRulesFileName);
1014 return;
1015 }
1016
1017 parseRules(newRoot);
1018 }
1019 catch(...)
1020 {
1021 close(fd);
1022 }
1023
1024 close(fd);
1025 }
1026
1027 void
1028 Engine::parseRules(CFDictionaryRef rules)
1029 {
1030 CFDictionaryApplyFunction(rules, parseRuleCallback, this);
1031 }
1032
1033 void
1034 Engine::parseRuleCallback(const void *key, const void *value, void *context)
1035 {
1036 Engine *engine = reinterpret_cast<Engine *>(context);
1037 if (CFGetTypeID(key) != CFStringGetTypeID())
1038 return;
1039
1040 CFStringRef right = reinterpret_cast<CFStringRef>(key);
1041 engine->parseRule(right, reinterpret_cast<CFTypeRef>(value));
1042 }
1043
1044 void
1045 Engine::parseRule(CFStringRef cfRight, CFTypeRef cfRule)
1046 {
1047 char buffer[512];
1048 const char *ptr = CFStringGetCStringPtr(cfRight, kCFStringEncodingUTF8);
1049 if (ptr == NULL)
1050 {
1051 if (CFStringGetCString(cfRight, buffer, 512, kCFStringEncodingUTF8))
1052 ptr = buffer;
1053 }
1054
1055 string right(ptr);
1056 try
1057 {
1058 mRules[right] = Rule(cfRule);
1059 debug("authrule", "added rule for right \"%s\"", right.c_str());
1060 }
1061 catch (...)
1062 {
1063 Syslog::error("Rules file \"%s\" right \"%s\": rule is invalid", mRulesFileName, ptr);
1064 }
1065 }
1066
1067
1068 /*!
1069 @function AuthorizationEngine::getRule
1070
1071 Look up the Rule for a given right.
1072
1073 @param inRight (input) the right for which we want a rule.
1074
1075 @results The Rule for right
1076 */
1077 Rule
1078 Engine::getRule(const Right &inRight) const
1079 {
1080 string key(inRight.rightName());
1081 // Lock the rulemap
1082 StLock<Mutex> _(mLock);
1083 for (;;)
1084 {
1085 RuleMap::const_iterator it = mRules.find(key);
1086 if (it != mRules.end())
1087 {
1088 debug("authrule", "right \"%s\" using right expression \"%s\"", inRight.rightName(), key.c_str());
1089 return it->second;
1090 }
1091
1092 // no default rule
1093 assert (key.size());
1094
1095 // any reduction of a combination of two chars is futile
1096 if (key.size() > 2) {
1097 // find last dot with exception of possible dot at end
1098 string::size_type index = key.rfind('.', key.size() - 2);
1099 // cut right after found dot, or make it match default rule
1100 key = key.substr(0, index == string::npos ? 0 : index + 1);
1101 } else
1102 key.erase();
1103 }
1104 }
1105
1106 /*!
1107 @function AuthorizationEngine::authorize
1108
1109 @@@.
1110
1111 @param inRights (input) List of rights being requested for authorization.
1112 @param environment (optional/input) Environment containing information to be used during evaluation.
1113 @param flags (input) Optional flags @@@ see AuthorizationCreate for a description.
1114 @param inCredentials (input) Credentials already held by the caller.
1115 @param outCredentials (output/optional) Credentials obtained, used or refreshed during this call to authorize the requested rights.
1116 @param outRights (output/optional) Subset of inRights which were actually authorized.
1117
1118 @results Returns errAuthorizationSuccess if all rights requested are authorized, or if the kAuthorizationFlagPartialRights flag was specified. Might return other status values like errAuthorizationDenied, errAuthorizationCanceled or errAuthorizationInteractionNotAllowed
1119 */
1120 OSStatus
1121 Engine::authorize(const RightSet &inRights, const AuthorizationEnvironment *environment,
1122 AuthorizationFlags flags, const CredentialSet *inCredentials, CredentialSet *outCredentials,
1123 MutableRightSet *outRights, AuthorizationToken &auth)
1124 {
1125 CredentialSet credentials;
1126 MutableRightSet rights;
1127 OSStatus status = errAuthorizationSuccess;
1128
1129 // Get current time of day.
1130 CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
1131
1132 // Update rules from database if needed
1133 updateRules(now);
1134
1135 // Check if a credential was passed into the environment and we were asked to extend the rights
1136 if (environment && (flags & kAuthorizationFlagExtendRights))
1137 {
1138 const AuthorizationItem *username = NULL, *password = NULL;
1139 bool shared = false;
1140 for (UInt32 ix = 0; ix < environment->count; ++ix)
1141 {
1142 const AuthorizationItem &item = environment->items[ix];
1143 if (!strcmp(item.name, kAuthorizationEnvironmentUsername))
1144 username = &item;
1145 if (!strcmp(item.name, kAuthorizationEnvironmentPassword))
1146 password = &item;
1147 if (!strcmp(item.name, kAuthorizationEnvironmentShared))
1148 shared = true;
1149 }
1150
1151 if (username && password)
1152 {
1153 // Let's create a credential from the passed in username and password.
1154 Credential newCredential(string(reinterpret_cast<const char *>(username->value), username->valueLength),
1155 string(reinterpret_cast<const char *>(password->value), password->valueLength), shared);
1156 // If it's valid insert it into the credentials list. Normally this is
1157 // only done if it actually authorizes a requested right, but for this
1158 // special case (environment) we do it even when no rights are being requested.
1159 if (newCredential->isValid())
1160 credentials.insert(newCredential);
1161 }
1162 }
1163
1164 RightSet::const_iterator end = inRights.end();
1165 for (RightSet::const_iterator it = inRights.begin(); it != end; ++it)
1166 {
1167 // Get the rule for each right we are trying to obtain.
1168 OSStatus result = getRule(*it).evaluate(*it, environment, flags, now,
1169 inCredentials, credentials, auth);
1170 if (result == errAuthorizationSuccess)
1171 rights.push_back(*it);
1172 else if (result == errAuthorizationDenied || result == errAuthorizationInteractionNotAllowed)
1173 {
1174 if (!(flags & kAuthorizationFlagPartialRights))
1175 {
1176 status = result;
1177 break;
1178 }
1179 }
1180 else if (result == errAuthorizationCanceled)
1181 {
1182 status = result;
1183 break;
1184 }
1185 else
1186 {
1187 Syslog::error("Engine::authorize: Rule::evaluate returned %ld returning errAuthorizationInternal", result);
1188 status = errAuthorizationInternal;
1189 break;
1190 }
1191 }
1192
1193 if (outCredentials)
1194 outCredentials->swap(credentials);
1195 if (outRights)
1196 outRights->swap(rights);
1197
1198 return status;
1199 }
1200
1201 } // end namespace Authorization