2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
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
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.
20 * AuthorizationEngine.cpp
23 * Created by Michael Brouwer on Thu Oct 12 2000.
24 * Copyright (c) 2000 Apple Computer Inc. All rights reserved.
27 #include "AuthorizationEngine.h"
28 #include <Security/AuthorizationWalkers.h>
31 #include "authority.h"
33 #include <Security/AuthorizationTags.h>
34 #include <Security/logging.h>
35 #include <Security/cfutilities.h>
36 #include <Security/debugging.h>
39 #include <CoreFoundation/CFData.h>
40 #include <CoreFoundation/CFNumber.h>
41 #include <CoreFoundation/CFPropertyList.h>
49 #include <Security/checkpw.h>
51 // checkpw() that uses provided struct passwd
54 int checkpw_internal( const char* userName
, const char* password
, const struct passwd
*pw
);
57 namespace Authorization
{
61 // Errors to be thrown
63 Error::Error(int err
) : error(err
)
67 const char *Error::what() const throw()
68 { return "Authorization error"; }
70 CSSM_RETURN
Error::cssmError() const throw()
71 { return error
; } // @@@ eventually...
73 OSStatus
Error::osStatus() const throw()
76 void Error::throwMe(int err
) { throw Error(err
); }
80 // CredentialImpl class
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)
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)
93 // try short name first
94 const char *user
= username
.c_str();
95 struct passwd
*pw
= getpwnam(user
);
101 debug("autheval", "user %s not found, creating invalid credential", user
);
105 const char *passwd
= password
.c_str();
106 int checkpw_status
= checkpw_internal(user
, passwd
, pw
);
108 if (checkpw_status
!= CHECKPW_SUCCESS
)
110 debug("autheval", "checkpw() for user %s failed with error %d, creating invalid credential", user
, checkpw_status
);
114 debug("autheval", "checkpw() for user %s succeeded, creating%s credential",
115 user
, mShared
? " shared" : "");
117 mUsername
= string ( pw
->pw_name
);
128 CredentialImpl::~CredentialImpl()
133 CredentialImpl::operator < (const CredentialImpl
&other
) const
135 if (!mShared
&& other
.mShared
)
137 if (!other
.mShared
&& mShared
)
140 return mUsername
< other
.mUsername
;
143 // Returns true if this CredentialImpl should be shared.
145 CredentialImpl::isShared() const
152 CredentialImpl::merge(const CredentialImpl
&other
)
154 assert(mUsername
== other
.mUsername
);
156 if (other
.mValid
&& (!mValid
|| mCreationTime
< other
.mCreationTime
))
158 mCreationTime
= other
.mCreationTime
;
165 // The time at which this credential was obtained.
167 CredentialImpl::creationTime() const
169 return mCreationTime
;
172 // Return true iff this credential is valid.
174 CredentialImpl::isValid() const
180 CredentialImpl::invalidate()
188 Credential::Credential() :
189 RefPointer
<CredentialImpl
>(NULL
)
193 Credential::Credential(CredentialImpl
*impl
) :
194 RefPointer
<CredentialImpl
>(impl
)
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
))
203 Credential::Credential(const string
&username
, const string
&password
, bool shared
) :
204 RefPointer
<CredentialImpl
>(new CredentialImpl(username
, password
, shared
))
208 Credential::~Credential()
213 Credential::operator < (const Credential
&other
) const
221 return (**this) < (*other
);
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");
238 mType(kUserInGroup
), mGroupName("admin"), mMaxCredentialAge(300.0), mShared(true), mAllowRoot(false)
240 // @@@ Default rule is shared admin group with 5 minute timeout
243 Rule::Rule(CFTypeRef cfRule
)
245 // @@@ This code is ugly. Serves me right for using CF.
246 if (CFGetTypeID(cfRule
) == CFStringGetTypeID())
248 CFStringRef tag
= reinterpret_cast<CFStringRef
>(cfRule
);
249 if (CFEqual(kAllowID
, tag
))
251 debug("authrule", "rule always allow");
254 else if (CFEqual(kDenyID
, tag
))
256 debug("authrule", "rule always deny");
262 else if (CFGetTypeID(cfRule
) == CFDictionaryGetTypeID())
264 CFDictionaryRef dict
= reinterpret_cast<CFDictionaryRef
>(cfRule
);
265 CFTypeRef groupTag
= CFDictionaryGetValue(dict
, kUserInGroupID
);
267 // Probably a user in group rule
270 if (CFGetTypeID(groupTag
) != CFStringGetTypeID())
273 mType
= kUserInGroup
;
275 CFStringRef group
= reinterpret_cast<CFStringRef
>(groupTag
);
277 const char *ptr
= CFStringGetCStringPtr(group
, kCFStringEncodingUTF8
);
280 if (CFStringGetCString(group
, buffer
, 512, kCFStringEncodingUTF8
))
286 mGroupName
= string(ptr
);
288 mMaxCredentialAge
= DBL_MAX
;
289 CFTypeRef timeoutTag
= CFDictionaryGetValue(dict
, kTimeoutID
);
292 if (CFGetTypeID(timeoutTag
) != CFNumberGetTypeID())
294 CFNumberGetValue(reinterpret_cast<CFNumberRef
>(timeoutTag
), kCFNumberDoubleType
, &mMaxCredentialAge
);
297 CFTypeRef sharedTag
= CFDictionaryGetValue(dict
, kSharedID
);
301 if (CFGetTypeID(sharedTag
) != CFBooleanGetTypeID())
303 mShared
= CFBooleanGetValue(reinterpret_cast<CFBooleanRef
>(sharedTag
));
306 CFTypeRef allowRootTag
= CFDictionaryGetValue(dict
, kAllowRootID
);
310 if (CFGetTypeID(allowRootTag
) != CFBooleanGetTypeID())
312 mAllowRoot
= CFBooleanGetValue(reinterpret_cast<CFBooleanRef
>(allowRootTag
));
314 debug("authrule", "rule user in group \"%s\" timeout %g%s%s",
315 mGroupName
.c_str(), mMaxCredentialAge
, mShared
? " shared" : "",
316 mAllowRoot
? " allow-root" : "");
320 CFTypeRef mechTag
= CFDictionaryGetValue(dict
, kEvalMechID
);
323 if (CFGetTypeID(mechTag
) != CFStringGetTypeID())
328 CFStringRef eval
= reinterpret_cast<CFStringRef
>(mechTag
);
330 const char *ptr
= CFStringGetCStringPtr(eval
, kCFStringEncodingUTF8
);
333 if (CFStringGetCString(eval
, buffer
, 512, kCFStringEncodingUTF8
))
338 mEvalDef
= string(ptr
);
347 Rule::Rule(const Rule
&other
) :
349 mGroupName(other
.mGroupName
),
350 mMaxCredentialAge(other
.mMaxCredentialAge
),
351 mShared(other
.mShared
),
352 mAllowRoot(other
.mAllowRoot
),
353 mEvalDef(other
.mEvalDef
)
358 Rule::operator = (const Rule
&other
)
361 mGroupName
= other
.mGroupName
;
362 mMaxCredentialAge
= other
.mMaxCredentialAge
;
363 mShared
= other
.mShared
;
364 mAllowRoot
= other
.mAllowRoot
;
365 mEvalDef
= other
.mEvalDef
;
375 Rule::evaluateMechanism(const AuthorizationEnvironment
*environment
, AuthorizationToken
&auth
, CredentialSet
&outCredentials
)
377 assert(mType
== kEvalMech
);
379 if (mEvalDef
.length() == 0) // no definition
380 return kAuthorizationResultAllow
;
382 // mechanisms are split by commas
383 vector
<string
> mechanismNames
;
385 string::size_type cursor
= 0, comma
= 0;
388 while (cursor
< mEvalDef
.length())
390 comma
= mEvalDef
.find(',', cursor
);
391 if (comma
== string::npos
)
392 comma
= mEvalDef
.length();
394 token
= mEvalDef
.substr(cursor
, comma
- cursor
);
397 if (token
.length() > 0)
398 mechanismNames
.push_back(token
);
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;
411 CssmAllocator
& alloc
= CssmAllocator::standard();
413 AuthorizationResult result
= kAuthorizationResultAllow
;
414 vector
<string
>::iterator currentMechanism
= mechanismNames
.begin();
416 while ( (result
== kAuthorizationResultAllow
) &&
417 (currentMechanism
!= mechanismNames
.end()) ) // iterate mechanisms
419 AuthorizationItemSet
*inHints
, *inContext
;
421 // release after invocation, ignored for first pass
424 inContext
= outContext
;
425 debug("SSevalMech", "set up context %p as input", inContext
);
427 context
= new MutableAuthItemSet(inContext
);
431 inContext
= &auth
.infoSet(); // returns deep copy
432 debug("SSevalMech", "set up stored context %p as input", inContext
);
434 context
= new MutableAuthItemSet(inContext
);
440 debug("SSevalMech", "set up hints %p as input", inHints
);
442 hints
= new AuthItemSet(outHints
);
447 debug("SSevalMech", "set up environment hints %p as input", environment
);
449 hints
= new AuthItemSet(environment
);
452 string::size_type extPlugin
= currentMechanism
->find(':');
453 if (extPlugin
!= string::npos
)
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());
460 bool mechExecOk
= false; // successfully ran a mechanism
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
);
470 mechExecOk
= client(pluginIn
, mechanismIn
, &arguments
, *hints
, *context
, &result
, outHints
, outContext
);
471 debug("SSevalMech", "new context %p, new hints %p", outContext
, outHints
);
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
;
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");
483 // Things worked and there is new context, so get rid of old
488 debug("SSevalMech", "release input context %p", inContext
);
489 alloc
.free(inContext
);
493 debug("SSevalMech", "release input hints %p", inHints
);
499 // reset previous context and hints
500 debug("SSevalMech", "resetting previous input context %p and hints %p", inContext
, inHints
);
501 outContext
= inContext
;
507 // internal mechanisms - no glue
508 if (*currentMechanism
== "authinternal")
510 debug("SSevalMech", "evaluate authinternal");
511 result
= kAuthorizationResultDeny
;
513 MutableAuthItemSet::iterator found
= find_if(context
->begin(), context
->end(), FindAuthItemByRightName(kAuthorizationEnvironmentUsername
) );
514 if (found
== context
->end())
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())
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())
526 outCredentials
.clear(); // only keep last one
527 debug("SSevalMech", "inserting new credential");
528 outCredentials
.insert(newCredential
);
529 result
= kAuthorizationResultAllow
;
531 result
= kAuthorizationResultDeny
;
535 if (*currentMechanism
== "push_hints_to_context")
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
543 debug("SSevalMech", "release input context %p", inContext
);
544 alloc
.free(inContext
);
546 // create out context from input hints, no merge
547 // @@@ global copy template not being invoked...
548 outContext
= Copier
<AuthorizationItemSet
>(*hints
).keep();
551 if (*currentMechanism
== "switch_to_user")
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
);
563 result
= kAuthorizationResultAllow
;
571 // we own outHints and outContext
574 case kAuthorizationResultAllow
:
575 debug("SSevalMech", "result allow");
578 case kAuthorizationResultDeny
:
579 debug("SSevalMech", "result deny");
582 debug("SSevalMech", "abort eval, release input context %p", inContext
);
583 alloc
.free(inContext
);
587 debug("SSevalMech", "abort eval, release input hints %p", inHints
);
590 outContext
= outHints
= NULL
; // making sure things get reset
593 currentMechanism
= mechanismNames
.begin();
594 result
= kAuthorizationResultAllow
; // stay in loop
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
604 break; // abort evaluation
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
612 debug("SSevalMech", "make new context %p available", outContext
);
613 auth
.setInfoSet(*outContext
);
617 // clean up last outContext and outHints, if any
620 debug("SSevalMech", "release output context %p", outContext
);
621 alloc
.free(outContext
);
625 debug("SSevalMech", "release output hints %p", outHints
);
626 alloc
.free(outHints
);
629 // deny on user cancel
632 case kAuthorizationResultUndefined
:
633 return errAuthorizationDenied
;
634 case kAuthorizationResultDeny
:
635 return errAuthorizationDenied
;
637 return errAuthorizationSuccess
; // @@@ cancel should return cancelled
642 Rule::evaluate(const Right
&inRight
,
643 const AuthorizationEnvironment
*environment
, AuthorizationFlags flags
,
644 CFAbsoluteTime now
, const CredentialSet
*inCredentials
, CredentialSet
&credentials
,
645 AuthorizationToken
&auth
)
650 debug("autheval", "rule is always allow");
651 return errAuthorizationSuccess
;
653 debug("autheval", "rule is always deny");
654 return errAuthorizationDenied
;
656 debug("autheval", "rule is user in group");
659 debug("autheval", "rule evalutes mechanisms");
660 return evaluateMechanism(environment
, auth
, credentials
);
665 // If we got here, this is a kUserInGroup type rule, let's start looking for a
666 // credential that is satisfactory
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)
672 debug("autheval", "creator of authorization has uid == 0 granting right %s",
673 inRight
.rightName());
674 return errAuthorizationSuccess
;
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
)
680 OSStatus status
= evaluate(inRight
, environment
, now
, *it
, true);
681 if (status
!= errAuthorizationDenied
)
683 // add credential to authinfo
684 auth
.setCredentialInfo(*it
);
689 // Second -- go though the credentials passed in to this authorize operation by the state management layer.
692 for (CredentialSet::const_iterator it
= inCredentials
->begin(); it
!= inCredentials
->end(); ++it
)
694 OSStatus status
= evaluate(inRight
, environment
, now
, *it
, false);
695 if (status
== errAuthorizationSuccess
)
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
);
705 else if (status
!= errAuthorizationDenied
)
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
;
715 if (!(flags
& kAuthorizationFlagInteractionAllowed
))
716 return errAuthorizationInteractionNotAllowed
;
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
);
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
);
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
) {
737 // user long name as hint
738 usernamehint
= string( pw
->pw_gecos
);
740 // minus other gecos crud
741 size_t comma
= usernamehint
.find(',');
743 usernamehint
= usernamehint
.substr(0, comma
);
744 // or fallback to short username
746 if (usernamehint
.size() == 0)
747 usernamehint
= string( pw
->pw_name
);
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
)
760 // Obtain a new credential. Anything but success is considered an error.
761 OSStatus status
= obtainCredential(query
, inRight
, environment
, usernamehint
.c_str(), newCredential
, reason
);
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
;
769 status
= evaluate(inRight
, environment
, now
, newCredential
, true);
770 if (status
== errAuthorizationSuccess
)
772 // Add the new credential we obtained to the output set.
773 // @@@ Deal with potential credential merges.
774 credentials
.insert(newCredential
);
777 // add credential to authinfo
778 auth
.setCredentialInfo(newCredential
);
780 return errAuthorizationSuccess
;
782 else if (status
!= errAuthorizationDenied
)
784 reason
= SecurityAgent::userNotInGroup
;
788 query
.cancel(SecurityAgent::tooManyTries
);
789 return errAuthorizationDenied
;
792 // Return errAuthorizationSuccess if this rule allows access based on the specified credential,
793 // return errAuthorizationDenied otherwise.
795 Rule::evaluate(const Right
&inRight
, const AuthorizationEnvironment
*environment
, CFAbsoluteTime now
,
796 const Credential
&credential
, bool ignoreShared
)
798 assert(mType
== kUserInGroup
);
800 // Get the username from the credential
801 const char *user
= credential
->username().c_str();
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())
807 debug("autheval", "credential for user %s is invalid, denying right %s", user
, inRight
.rightName());
808 return errAuthorizationDenied
;
811 if (now
- credential
->creationTime() > mMaxCredentialAge
)
813 debug("autheval", "credential for user %s has expired, denying right %s", user
, inRight
.rightName());
814 return errAuthorizationDenied
;
817 if (!ignoreShared
&& !mShared
&& credential
->isShared())
819 debug("autheval", "shared credential for user %s cannot be used, denying right %s", user
, inRight
.rightName());
820 return errAuthorizationDenied
;
823 // A root (uid == 0) user can do anything
824 if (credential
->uid() == 0)
826 debug("autheval", "user %s has uid 0, granting right %s", user
, inRight
.rightName());
827 return errAuthorizationSuccess
;
830 const char *groupname
= mGroupName
.c_str();
831 struct group
*gr
= getgrnam(groupname
);
833 return errAuthorizationDenied
;
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
)
840 debug("autheval", "user %s has group %s(%d) as default group, granting right %s",
841 user
, groupname
, gr
->gr_gid
, inRight
.rightName());
843 return errAuthorizationSuccess
;
846 for (char **group
= gr
->gr_mem
; *group
; ++group
)
848 if (!strcmp(*group
, user
))
850 debug("autheval", "user %s is a member of group %s, granting right %s",
851 user
, groupname
, inRight
.rightName());
853 return errAuthorizationSuccess
;
857 debug("autheval", "user %s is not a member of group %s, denying right %s",
858 user
, groupname
, inRight
.rightName());
860 return errAuthorizationDenied
;
864 Rule::obtainCredential(QueryAuthorizeByGroup
&query
, const Right
&inRight
,
865 const AuthorizationEnvironment
*environment
, const char *usernameHint
, Credential
&outCredential
, SecurityAgent::Reason reason
)
867 char nameBuffer
[SecurityAgent::maxUsernameLength
];
868 char passphraseBuffer
[SecurityAgent::maxPassphraseLength
];
869 OSStatus status
= errAuthorizationDenied
;
872 if (query(mGroupName
.c_str(), usernameHint
, nameBuffer
, passphraseBuffer
, reason
))
874 } catch (const CssmCommonError
&err
) {
875 status
= err
.osStatus();
877 status
= errAuthorizationInternal
;
879 if (status
== CSSM_ERRCODE_USER_CANCELED
)
881 debug("auth", "canceled obtaining credential for user in group %s", mGroupName
.c_str());
882 return errAuthorizationCanceled
;
884 if (status
== CSSM_ERRCODE_NO_USER_INTERACTION
)
886 debug("auth", "user interaction not possible obtaining credential for user in group %s", mGroupName
.c_str());
887 return errAuthorizationInteractionNotAllowed
;
892 debug("auth", "failed obtaining credential for user in group %s", mGroupName
.c_str());
896 debug("auth", "obtained credential for user %s", nameBuffer
);
898 string
username(nameBuffer
);
899 string
password(passphraseBuffer
);
900 outCredential
= Credential(username
, password
, mShared
);
901 return errAuthorizationSuccess
;
908 Engine::Engine(const char *configFile
) :
909 mLastChecked(DBL_MIN
)
911 mRulesFileName
= new char[strlen(configFile
) + 1];
912 strcpy(mRulesFileName
, configFile
);
913 memset(&mRulesFileMtimespec
, 0, sizeof(mRulesFileMtimespec
));
918 delete[] mRulesFileName
;
922 Engine::updateRules(CFAbsoluteTime now
)
924 StLock
<Mutex
> _(mLock
);
929 // Don't do anything if we checked the timestamp less than 5 seconds ago
930 if (mLastChecked
> now
- 5.0)
934 if (stat(mRulesFileName
, &st
))
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);
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
)))
956 // Make an entry in the mRules map that matches every right to the default Rule.
958 mRules
.insert(RuleMap::value_type(string(), Rule()));
960 int fd
= open(mRulesFileName
, O_RDONLY
, 0);
963 Syslog::error("Opening rules file \"%s\": %s", mRulesFileName
, strerror(errno
));
971 UnixError::throwMe(errno
);
973 mRulesFileMtimespec
= st
.st_mtimespec
;
975 off_t fileSize
= st
.st_size
;
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
)
983 if (bytesRead
== static_cast<size_t>(-1))
985 Syslog::error("Reading rules file \"%s\": %s", mRulesFileName
, strerror(errno
));
989 Syslog::error("Could only read %ul out of %ul bytes from rules file \"%s\"",
990 bytesRead
, fileSize
, mRulesFileName
);
994 CFStringRef errorString
;
995 CFRef
<CFDictionaryRef
> newRoot(reinterpret_cast<CFDictionaryRef
>
996 (CFPropertyListCreateFromXMLData(NULL
, xmlData
, kCFPropertyListImmutable
, &errorString
)));
1000 const char *error
= CFStringGetCStringPtr(errorString
, kCFStringEncodingUTF8
);
1003 if (CFStringGetCString(errorString
, buffer
, 512, kCFStringEncodingUTF8
))
1007 Syslog::error("Parsing rules file \"%s\": %s", mRulesFileName
, error
);
1011 if (CFGetTypeID(newRoot
) != CFDictionaryGetTypeID())
1013 Syslog::error("Rules file \"%s\": is not a dictionary", mRulesFileName
);
1017 parseRules(newRoot
);
1028 Engine::parseRules(CFDictionaryRef rules
)
1030 CFDictionaryApplyFunction(rules
, parseRuleCallback
, this);
1034 Engine::parseRuleCallback(const void *key
, const void *value
, void *context
)
1036 Engine
*engine
= reinterpret_cast<Engine
*>(context
);
1037 if (CFGetTypeID(key
) != CFStringGetTypeID())
1040 CFStringRef right
= reinterpret_cast<CFStringRef
>(key
);
1041 engine
->parseRule(right
, reinterpret_cast<CFTypeRef
>(value
));
1045 Engine::parseRule(CFStringRef cfRight
, CFTypeRef cfRule
)
1048 const char *ptr
= CFStringGetCStringPtr(cfRight
, kCFStringEncodingUTF8
);
1051 if (CFStringGetCString(cfRight
, buffer
, 512, kCFStringEncodingUTF8
))
1058 mRules
[right
] = Rule(cfRule
);
1059 debug("authrule", "added rule for right \"%s\"", right
.c_str());
1063 Syslog::error("Rules file \"%s\" right \"%s\": rule is invalid", mRulesFileName
, ptr
);
1069 @function AuthorizationEngine::getRule
1071 Look up the Rule for a given right.
1073 @param inRight (input) the right for which we want a rule.
1075 @results The Rule for right
1078 Engine::getRule(const Right
&inRight
) const
1080 string
key(inRight
.rightName());
1082 StLock
<Mutex
> _(mLock
);
1085 RuleMap::const_iterator it
= mRules
.find(key
);
1086 if (it
!= mRules
.end())
1088 debug("authrule", "right \"%s\" using right expression \"%s\"", inRight
.rightName(), key
.c_str());
1093 assert (key
.size());
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);
1107 @function AuthorizationEngine::authorize
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.
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
1121 Engine::authorize(const RightSet
&inRights
, const AuthorizationEnvironment
*environment
,
1122 AuthorizationFlags flags
, const CredentialSet
*inCredentials
, CredentialSet
*outCredentials
,
1123 MutableRightSet
*outRights
, AuthorizationToken
&auth
)
1125 CredentialSet credentials
;
1126 MutableRightSet rights
;
1127 OSStatus status
= errAuthorizationSuccess
;
1129 // Get current time of day.
1130 CFAbsoluteTime now
= CFAbsoluteTimeGetCurrent();
1132 // Update rules from database if needed
1135 // Check if a credential was passed into the environment and we were asked to extend the rights
1136 if (environment
&& (flags
& kAuthorizationFlagExtendRights
))
1138 const AuthorizationItem
*username
= NULL
, *password
= NULL
;
1139 bool shared
= false;
1140 for (UInt32 ix
= 0; ix
< environment
->count
; ++ix
)
1142 const AuthorizationItem
&item
= environment
->items
[ix
];
1143 if (!strcmp(item
.name
, kAuthorizationEnvironmentUsername
))
1145 if (!strcmp(item
.name
, kAuthorizationEnvironmentPassword
))
1147 if (!strcmp(item
.name
, kAuthorizationEnvironmentShared
))
1151 if (username
&& password
)
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
);
1164 RightSet::const_iterator end
= inRights
.end();
1165 for (RightSet::const_iterator it
= inRights
.begin(); it
!= end
; ++it
)
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
)
1174 if (!(flags
& kAuthorizationFlagPartialRights
))
1180 else if (result
== errAuthorizationCanceled
)
1187 Syslog::error("Engine::authorize: Rule::evaluate returned %ld returning errAuthorizationInternal", result
);
1188 status
= errAuthorizationInternal
;
1194 outCredentials
->swap(credentials
);
1196 outRights
->swap(rights
);
1201 } // end namespace Authorization