]> git.saurik.com Git - apple/security.git/blob - SecurityServer/Authorization/AuthorizationEngine.cpp
Security-28.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
28 #include "AuthorizationEngine.h"
29
30 #include "server.h"
31 #include "authority.h"
32
33 #include <Security/AuthorizationTags.h>
34 #include <Security/logging.h>
35 #include <Security/debugging.h>
36
37 #include <CoreFoundation/CFData.h>
38 #include <CoreFoundation/CFNumber.h>
39 #include <CoreFoundation/CFPropertyList.h>
40
41 #include <errno.h>
42 #include <fcntl.h>
43 #include <float.h>
44 #include <unistd.h>
45 #include <grp.h>
46 #include <pwd.h>
47
48 // for longname lookup
49 #include <netinfo/ni.h>
50 // private header (lu_utils.h from lookup project)
51 extern "C" {
52 int lookupd_query(ni_proplist *l, ni_proplist ***out);
53 ni_proplist *lookupd_make_query(char *cat, char *fmt, ...);
54 int _lu_running(void);
55 }
56
57 using namespace Authorization;
58
59 //
60 // Errors to be thrown
61 //
62 Error::Error(int err) : error(err)
63 {
64 }
65
66 const char *Error::what() const
67 { return "Authorization error"; }
68
69 CSSM_RETURN Error::cssmError() const
70 { return error; } // @@@ eventually...
71
72 OSStatus Error::osStatus() const
73 { return error; }
74
75 void Error::throwMe(int err) { throw Error(err); }
76
77
78 //
79 // CredentialImpl class
80 //
81
82 // only for testing whether this credential is usable
83 CredentialImpl::CredentialImpl(const string &username, const uid_t uid, const gid_t gid, bool shared) :
84 mUsername(username), mShared(shared), mUid(uid), mGid(gid), mCreationTime(CFAbsoluteTimeGetCurrent()), mValid(true)
85 {
86 }
87
88 // credential with validity based on username/password combination.
89 CredentialImpl::CredentialImpl(const string &username, const string &password, bool shared) :
90 mShared(shared), mCreationTime(CFAbsoluteTimeGetCurrent()), mValid(false)
91 {
92 // try short name first
93 const char *user = username.c_str();
94 struct passwd *pw = getpwnam(user);
95
96 do
97 {
98 if ( !pw && _lu_running() ) {
99 // try lookup query to find passed username as a long name (realname in NI-speak)
100 ni_proplist **out = NULL;
101 // query "user" records. "k" specifies position of keys in varargs
102 ni_proplist *in = lookupd_make_query("user", "kv", "realname", user);
103 if (!in) break;
104
105 int results = lookupd_query(in, &out);
106 ni_proplist_free(in);
107 if (!out) break;
108
109 // Find the first, if any, name value in returned records, getpwnam, and dispose of them
110 for (int i=0; i<results; ++i) {
111 ni_proplist *nipl = out[i];
112 for (unsigned int j=0; !pw && j< nipl->ni_proplist_len; j++) {
113 if ( !strcmp(nipl->ni_proplist_val[j].nip_name, "name") &&
114 (nipl->ni_proplist_val[j].nip_val.ni_namelist_len > 0) )
115 pw = getpwnam( *(nipl->ni_proplist_val[j].nip_val.ni_namelist_val) );
116 }
117 ni_proplist_free(nipl);
118 }
119 free(out);
120 }
121
122 if (!pw)
123 {
124 debug("autheval", "user %s not found, creating invalid credential", user);
125 break;
126 }
127
128 if (pw->pw_passwd != NULL && pw->pw_passwd[0])
129 {
130 const char *passwd = password.c_str();
131 if (strcmp(crypt(passwd, pw->pw_passwd), pw->pw_passwd))
132 {
133 debug("autheval", "password for user %s is invalid, creating invalid credential", user);
134 break;
135 }
136 }
137
138 debug("autheval", "password for user %s is ok, creating%s credential",
139 user, mShared ? " shared" : "");
140
141 mUsername = string ( pw->pw_name );
142 mUid = pw->pw_uid;
143 mGid = pw->pw_gid;
144 mValid = true;
145 }
146 while (0);
147
148 if (pw)
149 endpwent();
150 }
151
152
153 CredentialImpl::~CredentialImpl()
154 {
155 }
156
157 bool
158 CredentialImpl::operator < (const CredentialImpl &other) const
159 {
160 if (!mShared && other.mShared)
161 return true;
162 if (!other.mShared && mShared)
163 return false;
164
165 return mUsername < other.mUsername;
166 }
167
168 // Returns true if this CredentialImpl should be shared.
169 bool
170 CredentialImpl::isShared() const
171 {
172 return mShared;
173 }
174
175 // Merge with other
176 void
177 CredentialImpl::merge(const CredentialImpl &other)
178 {
179 assert(mUsername == other.mUsername);
180
181 if (other.mValid && (!mValid || mCreationTime < other.mCreationTime))
182 {
183 mCreationTime = other.mCreationTime;
184 mUid = other.mUid;
185 mGid = other.mGid;
186 mValid = true;
187 }
188 }
189
190 // The time at which this credential was obtained.
191 CFAbsoluteTime
192 CredentialImpl::creationTime() const
193 {
194 return mCreationTime;
195 }
196
197 // Return true iff this credential is valid.
198 bool
199 CredentialImpl::isValid() const
200 {
201 return mValid;
202 }
203
204 void
205 CredentialImpl::invalidate()
206 {
207 mValid = false;
208 }
209
210 //
211 // Credential class
212 //
213 Credential::Credential() :
214 RefPointer<CredentialImpl>(NULL)
215 {
216 }
217
218 Credential::Credential(CredentialImpl *impl) :
219 RefPointer<CredentialImpl>(impl)
220 {
221 }
222
223 Credential::Credential(const string &username, const uid_t uid, const gid_t gid, bool shared) :
224 RefPointer<CredentialImpl>(new CredentialImpl(username, uid, gid, shared))
225 {
226 }
227
228 Credential::Credential(const string &username, const string &password, bool shared) :
229 RefPointer<CredentialImpl>(new CredentialImpl(username, password, shared))
230 {
231 }
232
233 Credential::~Credential()
234 {
235 }
236
237 bool
238 Credential::operator < (const Credential &other) const
239 {
240 if (!*this)
241 return other;
242
243 if (!other)
244 return false;
245
246 return (**this) < (*other);
247 }
248
249
250 //
251 // Right class
252 //
253 Right &
254 Right::overlay(AuthorizationItem &item)
255 {
256 return static_cast<Right &>(item);
257 }
258
259 Right *
260 Right::overlay(AuthorizationItem *item)
261 {
262 return static_cast<Right *>(item);
263 }
264
265 Right::Right()
266 {
267 name = "";
268 valueLength = 0;
269 value = NULL;
270 flags = 0;
271 }
272
273 Right::Right(AuthorizationString inName, size_t inValueLength, const void *inValue)
274 {
275 name = inName;
276 valueLength = inValueLength;
277 value = const_cast<void *>(inValue);
278 }
279
280 Right::~Right()
281 {
282 }
283
284 bool
285 Right::operator < (const Right &other) const
286 {
287 return strcmp(name, other.name) < 0;
288 }
289
290
291 //
292 // RightSet class
293 //
294 const AuthorizationRights RightSet::gEmptyRights = { 0, NULL };
295
296 RightSet::RightSet(const AuthorizationRights *rights) :
297 mRights(const_cast<AuthorizationRights *>(rights ? rights : &gEmptyRights))
298 {
299 }
300
301 RightSet::RightSet(const RightSet &other)
302 {
303 mRights = other.mRights;
304 }
305
306 RightSet::~RightSet()
307 {
308 }
309
310 RightSet::const_reference
311 RightSet::back() const
312 {
313 // @@@ Should this if empty::throwMe()?
314 return static_cast<const_reference>(mRights->items[size() - 1]);
315 }
316
317
318 //
319 // MutableRightSet class
320 //
321 MutableRightSet::MutableRightSet(size_t count, const Right &element) :
322 mCapacity(count)
323 {
324 mRights = new AuthorizationRights();
325 mRights->items = reinterpret_cast<pointer>(malloc(sizeof(Right) * mCapacity));
326 if (!mRights->items)
327 {
328 delete mRights;
329 throw std::bad_alloc();
330 }
331
332 mRights->count = count;
333 for (size_type ix = 0; ix < count; ++ix)
334 mRights->items[ix] = element;
335 }
336
337 MutableRightSet::MutableRightSet(const RightSet &other)
338 {
339 size_type count = other.size();
340 mCapacity = count;
341 mRights = new AuthorizationRights();
342
343 mRights->items = reinterpret_cast<pointer>(malloc(sizeof(Right) * mCapacity));
344 if (!mRights->items)
345 {
346 delete mRights;
347 throw std::bad_alloc();
348 }
349
350 mRights->count = count;
351 for (size_type ix = 0; ix < count; ++ix)
352 mRights->items[ix] = other.mRights->items[ix];
353 }
354
355 MutableRightSet::~MutableRightSet()
356 {
357 free(mRights->items);
358 delete mRights;
359 }
360
361 MutableRightSet &
362 MutableRightSet::operator = (const RightSet &other)
363 {
364 size_type count = other.size();
365 if (capacity() < count)
366 grow(count);
367
368 mRights->count = count;
369 for (size_type ix = 0; ix < count; ++ix)
370 mRights->items[ix] = other.mRights->items[ix];
371
372 return *this;
373 }
374
375 void
376 MutableRightSet::swap(MutableRightSet &other)
377 {
378 AuthorizationRights *rights = mRights;
379 size_t capacity = mCapacity;
380 mRights = other.mRights;
381 mCapacity = other.mCapacity;
382 other.mRights = rights;
383 other.mCapacity = capacity;
384 }
385
386 MutableRightSet::reference
387 MutableRightSet::back()
388 {
389 // @@@ Should this if empty::throwMe()?
390 return static_cast<reference>(mRights->items[size() - 1]);
391 }
392
393 void
394 MutableRightSet::push_back(const_reference right)
395 {
396 if (size() >= capacity())
397 grow(capacity() + 1);
398
399 mRights->items[mRights->count] = right;
400 mRights->count++;
401 }
402
403 void
404 MutableRightSet::pop_back()
405 {
406 // @@@ Should this if empty::throwMe()?
407 if (!empty())
408 mRights->count--;
409 }
410
411 void
412 MutableRightSet::grow(size_type min_capacity)
413 {
414 size_type newCapacity = mCapacity * mCapacity;
415 if (newCapacity < min_capacity)
416 newCapacity = min_capacity;
417
418 void *newItems = realloc(mRights->items, sizeof(*mRights->items) * newCapacity);
419 if (!newItems)
420 throw std::bad_alloc();
421
422 mRights->items = reinterpret_cast<pointer>(newItems);
423 mCapacity = newCapacity;
424 }
425
426
427 //
428 // Rule class
429 //
430 CFStringRef Rule::kUserInGroupID = CFSTR("group");
431 CFStringRef Rule::kTimeoutID = CFSTR("timeout");
432 CFStringRef Rule::kSharedID = CFSTR("shared");
433 CFStringRef Rule::kAllowRootID = CFSTR("allow-root");
434 CFStringRef Rule::kDenyID = CFSTR("deny");
435 CFStringRef Rule::kAllowID = CFSTR("allow");
436
437
438 Rule::Rule() :
439 mType(kUserInGroup), mGroupName("admin"), mMaxCredentialAge(300.0), mShared(true), mAllowRoot(false)
440 {
441 // @@@ Default rule is shared admin group with 5 minute timeout
442 }
443
444 Rule::Rule(CFTypeRef cfRule)
445 {
446 // @@@ This code is ugly. Serves me right for using CF.
447 if (CFGetTypeID(cfRule) == CFStringGetTypeID())
448 {
449 CFStringRef tag = reinterpret_cast<CFStringRef>(cfRule);
450 if (CFEqual(kAllowID, tag))
451 {
452 debug("authrule", "rule always allow");
453 mType = kAllow;
454 }
455 else if (CFEqual(kDenyID, tag))
456 {
457 debug("authrule", "rule always deny");
458 mType = kDeny;
459 }
460 else
461 Error::throwMe();
462 }
463 else if (CFGetTypeID(cfRule) == CFDictionaryGetTypeID())
464 {
465 mType = kUserInGroup;
466 CFDictionaryRef dict = reinterpret_cast<CFDictionaryRef>(cfRule);
467 CFTypeRef groupTag = CFDictionaryGetValue(dict, kUserInGroupID);
468 if (!groupTag || CFGetTypeID(groupTag) != CFStringGetTypeID())
469 Error::throwMe();
470
471 CFStringRef group = reinterpret_cast<CFStringRef>(groupTag);
472 char buffer[512];
473 const char *ptr = CFStringGetCStringPtr(group, kCFStringEncodingUTF8);
474 if (ptr == NULL)
475 {
476 if (CFStringGetCString(group, buffer, 512, kCFStringEncodingUTF8))
477 ptr = buffer;
478 else
479 Error::throwMe();
480 }
481
482 mGroupName = string(ptr);
483
484 mMaxCredentialAge = DBL_MAX;
485 CFTypeRef timeoutTag = CFDictionaryGetValue(dict, kTimeoutID);
486 if (timeoutTag)
487 {
488 if (CFGetTypeID(timeoutTag) != CFNumberGetTypeID())
489 Error::throwMe();
490 CFNumberGetValue(reinterpret_cast<CFNumberRef>(timeoutTag), kCFNumberDoubleType, &mMaxCredentialAge);
491 }
492
493 CFTypeRef sharedTag = CFDictionaryGetValue(dict, kSharedID);
494 mShared = false;
495 if (sharedTag)
496 {
497 if (CFGetTypeID(sharedTag) != CFBooleanGetTypeID())
498 Error::throwMe();
499 mShared = CFBooleanGetValue(reinterpret_cast<CFBooleanRef>(sharedTag));
500 }
501
502 CFTypeRef allowRootTag = CFDictionaryGetValue(dict, kAllowRootID);
503 mAllowRoot = false;
504 if (allowRootTag)
505 {
506 if (CFGetTypeID(allowRootTag) != CFBooleanGetTypeID())
507 Error::throwMe();
508 mAllowRoot = CFBooleanGetValue(reinterpret_cast<CFBooleanRef>(allowRootTag));
509 }
510 debug("authrule", "rule user in group \"%s\" timeout %g%s%s",
511 mGroupName.c_str(), mMaxCredentialAge, mShared ? " shared" : "",
512 mAllowRoot ? " allow-root" : "");
513 }
514 }
515
516 Rule::Rule(const Rule &other) :
517 mType(other.mType),
518 mGroupName(other.mGroupName),
519 mMaxCredentialAge(other.mMaxCredentialAge),
520 mShared(other.mShared),
521 mAllowRoot(other.mAllowRoot)
522 {
523 }
524
525 Rule &
526 Rule::operator = (const Rule &other)
527 {
528 mType = other.mType;
529 mGroupName = other.mGroupName;
530 mMaxCredentialAge = other.mMaxCredentialAge;
531 mShared = other.mShared;
532 mAllowRoot = other.mAllowRoot;
533 return *this;
534 }
535
536 Rule::~Rule()
537 {
538 }
539
540 OSStatus
541 Rule::evaluate(const Right &inRight,
542 const AuthorizationEnvironment *environment, AuthorizationFlags flags,
543 CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials,
544 const AuthorizationToken &auth)
545 {
546 switch (mType)
547 {
548 case kAllow:
549 debug("autheval", "rule is always allow");
550 return errAuthorizationSuccess;
551 case kDeny:
552 debug("autheval", "rule is always deny");
553 return errAuthorizationDenied;
554 case kUserInGroup:
555 debug("autheval", "rule is user in group");
556 break;
557 default:
558 Error::throwMe();
559 }
560
561 // If we got here, this is a kUserInGroup type rule, let's start looking for a
562 // credential that is satisfactory
563
564 // Zeroth -- Here is an extra special saucy ugly hack to allow authorizations
565 // created by a proccess running as root to automatically get a right.
566 if (mAllowRoot && auth.creatorUid() == 0)
567 {
568 debug("autheval", "creator of authorization has uid == 0 granting right %s",
569 inRight.rightName());
570 return errAuthorizationSuccess;
571 }
572
573 // First -- go though the credentials we either already used or obtained during this authorize operation.
574 for (CredentialSet::const_iterator it = credentials.begin(); it != credentials.end(); ++it)
575 {
576 OSStatus status = evaluate(inRight, environment, now, *it, true);
577 if (status != errAuthorizationDenied)
578 return status;
579 }
580
581 // Second -- go though the credentials passed in to this authorize operation by the state management layer.
582 if (inCredentials)
583 {
584 for (CredentialSet::const_iterator it = inCredentials->begin(); it != inCredentials->end(); ++it)
585 {
586 OSStatus status = evaluate(inRight, environment, now, *it, false);
587 if (status == errAuthorizationSuccess)
588 {
589 // Add the credential we used to the output set.
590 // @@@ Deal with potential credential merges.
591 credentials.insert(*it);
592 return status;
593 }
594 else if (status != errAuthorizationDenied)
595 return status;
596 }
597 }
598
599 // Finally -- We didn't find the credential in our passed in credential lists. Obtain a new credential if
600 // our flags let us do so.
601 if (!(flags & kAuthorizationFlagExtendRights))
602 return errAuthorizationDenied;
603
604 if (!(flags & kAuthorizationFlagInteractionAllowed))
605 return errAuthorizationInteractionNotAllowed;
606
607 QueryAuthorizeByGroup query;
608
609 string usernamehint;
610 // @@@ This should really be the loginname of the proccess that originally created the AuthorizationRef.
611 // For now we get the pw_name of the user with the uid of the calling process.
612 uid_t uid = query.uid();
613 if (uid)
614 {
615 struct passwd *pw = getpwuid(uid);
616 if (pw != NULL)
617 {
618 // avoid hinting a locked account (ie. root)
619 if ( (pw->pw_passwd == NULL) ||
620 strcmp(pw->pw_passwd, "*") ) {
621 // Check if username will authorize the request and set username to
622 // be used as a hint to the user if so
623 if (evaluate(inRight, environment, now, Credential(pw->pw_name, pw->pw_uid, pw->pw_gid, mShared), true) == errAuthorizationSuccess) {
624 // user long name as hint
625 usernamehint = string( pw->pw_gecos );
626 // minus other gecos crud
627 size_t comma = usernamehint.find(',');
628 if (comma)
629 usernamehint = usernamehint.substr(0, comma);
630 // or fallback to short username
631 if (usernamehint.size() == 0)
632 usernamehint = string( pw->pw_name );
633 } //fi
634 } //fi
635 endpwent();
636 }
637 }
638
639 Credential newCredential;
640 // @@@ Keep the default reason the same, so the agent only gets userNotInGroup or invalidPassphrase
641 SecurityAgent::Reason reason = SecurityAgent::userNotInGroup;
642 // @@@ Hardcoded 3 tries to avoid infinite loops.
643 for (int tryCount = 0; tryCount < 3; ++tryCount)
644 {
645 // Obtain a new credential. Anything but success is considered an error.
646 OSStatus status = obtainCredential(query, inRight, environment, usernamehint.c_str(), newCredential, reason);
647 if (status)
648 return status;
649
650 // Now we have successfully obtained a credential we need to make sure it authorizes the requested right
651 if (!newCredential->isValid())
652 reason = SecurityAgent::invalidPassphrase;
653 else {
654 status = evaluate(inRight, environment, now, newCredential, true);
655 if (status == errAuthorizationSuccess)
656 {
657 // Add the new credential we obtained to the output set.
658 // @@@ Deal with potential credential merges.
659 credentials.insert(newCredential);
660 query.done();
661 return errAuthorizationSuccess;
662 }
663 else if (status != errAuthorizationDenied)
664 return status;
665 reason = SecurityAgent::userNotInGroup;
666 }
667 }
668
669 query.cancel(SecurityAgent::tooManyTries);
670 return errAuthorizationDenied;
671 }
672
673 // Return errAuthorizationSuccess if this rule allows access based on the specified credential,
674 // return errAuthorizationDenied otherwise.
675 OSStatus
676 Rule::evaluate(const Right &inRight, const AuthorizationEnvironment *environment, CFAbsoluteTime now,
677 const Credential &credential, bool ignoreShared)
678 {
679 assert(mType == kUserInGroup);
680
681 // Get the username from the credential
682 const char *user = credential->username().c_str();
683
684 // If the credential is not valid or it's age is more than the allowed maximum age
685 // for a credential, deny.
686 if (!credential->isValid())
687 {
688 debug("autheval", "credential for user %s is invalid, denying right %s", user, inRight.rightName());
689 return errAuthorizationDenied;
690 }
691
692 if (now - credential->creationTime() > mMaxCredentialAge)
693 {
694 debug("autheval", "credential for user %s has expired, denying right %s", user, inRight.rightName());
695 return errAuthorizationDenied;
696 }
697
698 if (!ignoreShared && !mShared && credential->isShared())
699 {
700 debug("autheval", "shared credential for user %s cannot be used, denying right %s", user, inRight.rightName());
701 return errAuthorizationDenied;
702 }
703
704 // A root (uid == 0) user can do anything
705 if (credential->uid() == 0)
706 {
707 debug("autheval", "user %s has uid 0, granting right %s", user, inRight.rightName());
708 return errAuthorizationSuccess;
709 }
710
711 const char *groupname = mGroupName.c_str();
712 struct group *gr = getgrnam(groupname);
713 if (!gr)
714 return errAuthorizationDenied;
715
716 // Is this the default group of this user?
717 // <grp.h> declares gr_gid int, as opposed to advertised (getgrent(3)) gid_t
718 if (credential->gid() == gr->gr_gid)
719 {
720 debug("autheval", "user %s has group %s(%d) as default group, granting right %s",
721 user, groupname, gr->gr_gid, inRight.rightName());
722 endgrent();
723 return errAuthorizationSuccess;
724 }
725
726 for (char **group = gr->gr_mem; *group; ++group)
727 {
728 if (!strcmp(*group, user))
729 {
730 debug("autheval", "user %s is a member of group %s, granting right %s",
731 user, groupname, inRight.rightName());
732 endgrent();
733 return errAuthorizationSuccess;
734 }
735 }
736
737 debug("autheval", "user %s is not a member of group %s, denying right %s",
738 user, groupname, inRight.rightName());
739 endgrent();
740 return errAuthorizationDenied;
741 }
742
743 OSStatus
744 Rule::obtainCredential(QueryAuthorizeByGroup &query, const Right &inRight,
745 const AuthorizationEnvironment *environment, const char *usernameHint, Credential &outCredential, SecurityAgent::Reason reason)
746 {
747 char nameBuffer[SecurityAgent::maxUsernameLength];
748 char passphraseBuffer[SecurityAgent::maxPassphraseLength];
749 OSStatus status = errAuthorizationDenied;
750
751 try {
752 if (query(mGroupName.c_str(), usernameHint, nameBuffer, passphraseBuffer, reason))
753 status = noErr;
754 } catch (const CssmCommonError &err) {
755 status = err.osStatus();
756 } catch (...) {
757 status = errAuthorizationInternal;
758 }
759 if (status == CSSM_ERRCODE_USER_CANCELED)
760 {
761 debug("auth", "canceled obtaining credential for user in group %s", mGroupName.c_str());
762 return errAuthorizationCanceled;
763 }
764 if (status == CSSM_ERRCODE_NO_USER_INTERACTION)
765 {
766 debug("auth", "user interaction not possible obtaining credential for user in group %s", mGroupName.c_str());
767 return errAuthorizationInteractionNotAllowed;
768 }
769
770 if (status != noErr)
771 {
772 debug("auth", "failed obtaining credential for user in group %s", mGroupName.c_str());
773 return status;
774 }
775
776 debug("auth", "obtained credential for user %s", nameBuffer);
777
778 string username(nameBuffer);
779 string password(passphraseBuffer);
780 outCredential = Credential(username, password, mShared);
781 return errAuthorizationSuccess;
782 }
783
784
785 //
786 // Engine class
787 //
788 Engine::Engine(const char *configFile) :
789 mLastChecked(DBL_MIN)
790 {
791 mRulesFileName = new char[strlen(configFile) + 1];
792 strcpy(mRulesFileName, configFile);
793 memset(&mRulesFileMtimespec, 0, sizeof(mRulesFileMtimespec));
794 }
795
796 Engine::~Engine()
797 {
798 delete[] mRulesFileName;
799 }
800
801 void
802 Engine::updateRules(CFAbsoluteTime now)
803 {
804 if (mRules.empty())
805 readRules();
806 else
807 {
808 // Don't do anything if we checked the timestamp less than 5 seconds ago
809 if (mLastChecked > now - 5.0)
810 return;
811
812 struct stat st;
813 if (stat(mRulesFileName, &st))
814 {
815 Syslog::error("Stating rules file \"%s\": %s", mRulesFileName, strerror(errno));
816 /* @@@ No rules file found, use defaults: admin group for everything. */
817 //UnixError::throwMe(errno);
818 }
819 else
820 {
821 // @@@ Make sure this is the right way to compare 2 struct timespec thingies
822 // Technically we should check st_dev and st_ino as well since if either of those change
823 // we are looking at a different file too.
824 if (memcmp(&st.st_mtimespec, &mRulesFileMtimespec, sizeof(mRulesFileMtimespec)))
825 readRules();
826 }
827 }
828
829 mLastChecked = now;
830 }
831
832 void
833 Engine::readRules()
834 {
835 // Make an entry in the mRules map that matches every right to the default Rule.
836 mRules.clear();
837 mRules.insert(RuleMap::value_type(string(), Rule()));
838
839 int fd = open(mRulesFileName, O_RDONLY, 0);
840 if (fd == -1)
841 {
842 Syslog::error("Opening rules file \"%s\": %s", mRulesFileName, strerror(errno));
843 return;
844 }
845
846 try
847 {
848 struct stat st;
849 if (fstat(fd, &st))
850 UnixError::throwMe(errno);
851
852 mRulesFileMtimespec = st.st_mtimespec;
853
854 off_t fileSize = st.st_size;
855
856 CFRef<CFMutableDataRef> xmlData(CFDataCreateMutable(NULL, fileSize));
857 CFDataSetLength(xmlData, fileSize);
858 void *buffer = CFDataGetMutableBytePtr(xmlData);
859 size_t bytesRead = read(fd, buffer, fileSize);
860 if (bytesRead != fileSize)
861 {
862 if (bytesRead == static_cast<size_t>(-1))
863 {
864 Syslog::error("Reading rules file \"%s\": %s", mRulesFileName, strerror(errno));
865 return;
866 }
867
868 Syslog::error("Could only read %ul out of %ul bytes from rules file \"%s\"",
869 bytesRead, fileSize, mRulesFileName);
870 return;
871 }
872
873 CFStringRef errorString;
874 CFRef<CFDictionaryRef> newRoot(reinterpret_cast<CFDictionaryRef>
875 (CFPropertyListCreateFromXMLData(NULL, xmlData, kCFPropertyListImmutable, &errorString)));
876 if (!newRoot)
877 {
878 char buffer[512];
879 const char *error = CFStringGetCStringPtr(errorString, kCFStringEncodingUTF8);
880 if (error == NULL)
881 {
882 if (CFStringGetCString(errorString, buffer, 512, kCFStringEncodingUTF8))
883 error = buffer;
884 }
885
886 Syslog::error("Parsing rules file \"%s\": %s", mRulesFileName, error);
887 return;
888 }
889
890 if (CFGetTypeID(newRoot) != CFDictionaryGetTypeID())
891 {
892 Syslog::error("Rules file \"%s\": is not a dictionary", mRulesFileName);
893 return;
894 }
895
896 parseRules(newRoot);
897 }
898 catch(...)
899 {
900 close(fd);
901 }
902
903 close(fd);
904 }
905
906 void
907 Engine::parseRules(CFDictionaryRef rules)
908 {
909 CFDictionaryApplyFunction(rules, parseRuleCallback, this);
910 }
911
912 void
913 Engine::parseRuleCallback(const void *key, const void *value, void *context)
914 {
915 Engine *engine = reinterpret_cast<Engine *>(context);
916 if (CFGetTypeID(key) != CFStringGetTypeID())
917 return;
918
919 CFStringRef right = reinterpret_cast<CFStringRef>(key);
920 engine->parseRule(right, reinterpret_cast<CFTypeRef>(value));
921 }
922
923 void
924 Engine::parseRule(CFStringRef cfRight, CFTypeRef cfRule)
925 {
926 char buffer[512];
927 const char *ptr = CFStringGetCStringPtr(cfRight, kCFStringEncodingUTF8);
928 if (ptr == NULL)
929 {
930 if (CFStringGetCString(cfRight, buffer, 512, kCFStringEncodingUTF8))
931 ptr = buffer;
932 }
933
934 string right(ptr);
935 try
936 {
937 mRules[right] = Rule(cfRule);
938 debug("authrule", "added rule for right \"%s\"", right.c_str());
939 }
940 catch (...)
941 {
942 Syslog::error("Rules file \"%s\" right \"%s\": rule is invalid", mRulesFileName, ptr);
943 }
944 }
945
946
947 /*!
948 @function AuthorizationEngine::getRule
949
950 Look up the Rule for a given right.
951
952 @param inRight (input) the right for which we want a rule.
953
954 @results The Rule for right
955 */
956 Rule
957 Engine::getRule(const Right &inRight) const
958 {
959 string key(inRight.rightName());
960 for (;;)
961 {
962 RuleMap::const_iterator it = mRules.find(key);
963 if (it != mRules.end())
964 {
965 debug("authrule", "right \"%s\" using right expression \"%s\"", inRight.rightName(), key.c_str());
966 return it->second;
967 }
968
969 // no default rule
970 assert (key.size());
971
972 // any reduction of a combination of two chars is futile
973 if (key.size() > 2) {
974 // find last dot with exception of possible dot at end
975 string::size_type index = key.rfind('.', key.size() - 2);
976 // cut right after found dot, or make it match default rule
977 key = key.substr(0, index == string::npos ? 0 : index + 1);
978 } else
979 key.erase();
980 }
981 }
982
983 /*!
984 @function AuthorizationEngine::authorize
985
986 @@@.
987
988 @param inRights (input) List of rights being requested for authorization.
989 @param environment (optional/input) Environment containing information to be used during evaluation.
990 @param flags (input) Optional flags @@@ see AuthorizationCreate for a description.
991 @param inCredentials (input) Credentials already held by the caller.
992 @param outCredentials (output/optional) Credentials obtained, used or refreshed during this call to authorize the requested rights.
993 @param outRights (output/optional) Subset of inRights which were actually authorized.
994
995 @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
996 */
997 OSStatus
998 Engine::authorize(const RightSet &inRights, const AuthorizationEnvironment *environment,
999 AuthorizationFlags flags, const CredentialSet *inCredentials, CredentialSet *outCredentials,
1000 MutableRightSet *outRights, const AuthorizationToken &auth)
1001 {
1002 CredentialSet credentials;
1003 MutableRightSet rights;
1004 OSStatus status = errAuthorizationSuccess;
1005
1006 // Get current time of day.
1007 CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
1008
1009 // Update rules from database if needed
1010 updateRules(now);
1011
1012 // Check if a credential was passed into the environment and we were asked to extend the rights
1013 if (environment && (flags & kAuthorizationFlagExtendRights))
1014 {
1015 const AuthorizationItem *username = NULL, *password = NULL;
1016 bool shared = false;
1017 for (UInt32 ix = 0; ix < environment->count; ++ix)
1018 {
1019 const AuthorizationItem &item = environment->items[ix];
1020 if (!strcmp(item.name, kAuthorizationEnvironmentUsername))
1021 username = &item;
1022 if (!strcmp(item.name, kAuthorizationEnvironmentPassword))
1023 password = &item;
1024 if (!strcmp(item.name, kAuthorizationEnvironmentShared))
1025 shared = true;
1026 }
1027
1028 if (username && password)
1029 {
1030 // Let's create a credential from the passed in username and password.
1031 Credential newCredential(string(reinterpret_cast<const char *>(username->value), username->valueLength),
1032 string(reinterpret_cast<const char *>(password->value), password->valueLength), shared);
1033 // If it's valid insert it into the credentials list. Normally this is
1034 // only done if it actually authorizes a requested right, but for this
1035 // special case (environment) we do it even when no rights are being requested.
1036 if (newCredential->isValid())
1037 credentials.insert(newCredential);
1038 }
1039 }
1040
1041 RightSet::const_iterator end = inRights.end();
1042 for (RightSet::const_iterator it = inRights.begin(); it != end; ++it)
1043 {
1044 // Get the rule for each right we are trying to obtain.
1045 OSStatus result = getRule(*it).evaluate(*it, environment, flags, now,
1046 inCredentials, credentials, auth);
1047 if (result == errAuthorizationSuccess)
1048 rights.push_back(*it);
1049 else if (result == errAuthorizationDenied || result == errAuthorizationInteractionNotAllowed)
1050 {
1051 if (!(flags & kAuthorizationFlagPartialRights))
1052 {
1053 status = result;
1054 break;
1055 }
1056 }
1057 else if (result == errAuthorizationCanceled)
1058 {
1059 status = result;
1060 break;
1061 }
1062 else
1063 {
1064 Syslog::error("Engine::authorize: Rule::evaluate returned %ld returning errAuthorizationInternal", result);
1065 status = errAuthorizationInternal;
1066 break;
1067 }
1068 }
1069
1070 if (outCredentials)
1071 outCredentials->swap(credentials);
1072 if (outRights)
1073 outRights->swap(rights);
1074
1075 return status;
1076 }