Authorization.cpp   [plain text]


/*
 * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved.
 * 
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */


//
// Authorization.cpp
//
// This file is the unified implementation of the Authorization and AuthSession APIs.
//
#include <stdint.h>
#include <Security/Authorization.h>
#include <Security/AuthorizationPriv.h>
#include <Security/AuthorizationDB.h>
#include <Security/AuthorizationTagsPriv.h>
#include <Security/AuthSession.h>
#include <security_utilities/mach++.h>
#include <security_utilities/globalizer.h>
#include <security_utilities/alloc.h>
#include <security_utilities/cfutilities.h>
#include <security_cdsa_utilities/cssmbridge.h>
#include <security_cdsa_utilities/AuthorizationWalkers.h>
#include <security_utilities/ccaudit.h>
#include <securityd_client/ssclient.h>
#include <CoreFoundation/CFPreferences.h>
#include <Carbon/../Frameworks/HIToolbox.framework/Headers/TextInputSources.h>

#include <security_utilities/logging.h>

using namespace SecurityServer;
using namespace MachPlusPlus;


//
// Shared cached client object
//
class AuthClient : public SecurityServer::ClientSession {
public:
	AuthClient()
	: SecurityServer::ClientSession(Allocator::standard(), Allocator::standard())
	{ }
};

static ModuleNexus<AuthClient> server;


//
// Create an Authorization
//
OSStatus AuthorizationCreate(const AuthorizationRights *rights,
	const AuthorizationEnvironment *environment,
	AuthorizationFlags flags,
	AuthorizationRef *authorization)
{
	BEGIN_API
	AuthorizationBlob result;
	server().authCreate(rights, environment, flags, result);
	if (authorization)
	{
		*authorization = 
			(AuthorizationRef) new(server().returnAllocator) AuthorizationBlob(result);
	}
	else
	{
		// If no authorizationRef is desired free the one we just created.
		server().authRelease(result, flags);
	}
	END_API(CSSM)
}


//
// Free an authorization reference
//
OSStatus AuthorizationFree(AuthorizationRef authorization, AuthorizationFlags flags)
{
	BEGIN_API
	AuthorizationBlob *auth = (AuthorizationBlob *)authorization;
	server().authRelease(Required(auth, errAuthorizationInvalidRef), flags);
	server().returnAllocator.free(auth);
	END_API(CSSM)
}


//
// Augment and/or interrogate an authorization
//
OSStatus AuthorizationCopyRights(AuthorizationRef authorization,
	const AuthorizationRights *rights,
	const AuthorizationEnvironment *environment,
	AuthorizationFlags flags,
	AuthorizationRights **authorizedRights)
{
	BEGIN_API
	AuthorizationBlob *auth = (AuthorizationBlob *)authorization;
	server().authCopyRights(Required(auth, errAuthorizationInvalidRef),
		rights, environment, flags, authorizedRights);
	END_API(CSSM)
}


//
// Augment and/or interrogate an authorization asynchronously
//
void AuthorizationCopyRightsAsync(AuthorizationRef authorization,
	const AuthorizationRights *rights,
	const AuthorizationEnvironment *environment,
	AuthorizationFlags flags,
	AuthorizationAsyncCallback callbackBlock)
{
	__block AuthorizationRights *blockAuthorizedRights = NULL;
	
	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
		OSStatus status = AuthorizationCopyRights(authorization, rights, environment, flags, &blockAuthorizedRights);
		callbackBlock(status, blockAuthorizedRights);
	});
}


//
// Retrieve side-band information from an authorization
//
OSStatus AuthorizationCopyInfo(AuthorizationRef authorization, 
	AuthorizationString tag,
	AuthorizationItemSet **info)
{
	BEGIN_API
	AuthorizationBlob *auth = (AuthorizationBlob *)authorization;
	server().authCopyInfo(Required(auth, errAuthorizationInvalidRef),
		tag, Required(info));
	END_API(CSSM)
}


//
// Externalize and internalize authorizations
//
OSStatus AuthorizationMakeExternalForm(AuthorizationRef authorization,
	AuthorizationExternalForm *extForm)
{
	BEGIN_API
	AuthorizationBlob *auth = (AuthorizationBlob *)authorization;
	server().authExternalize(Required(auth, errAuthorizationInvalidRef), *extForm);
	END_API(CSSM)
}

OSStatus AuthorizationCreateFromExternalForm(const AuthorizationExternalForm *extForm,
	AuthorizationRef *authorization)
{
	BEGIN_API
	AuthorizationBlob result;
	server().authInternalize(*extForm, result);
	Required(authorization, errAuthorizationInvalidRef) =
		(AuthorizationRef) new(server().returnAllocator) AuthorizationBlob(result);

	END_API(CSSM)
}


//
// Free an ItemSet structure returned from an API call. This is a local operation.
// Since we allocate returned ItemSets as compact blobs, this is just a simple
// free() call.
//
OSStatus AuthorizationFreeItemSet(AuthorizationItemSet *set)
{
	BEGIN_API
	server().returnAllocator.free(set);
	return errAuthorizationSuccess;
	END_API(CSSM)
}


//
// This no longer talks to securityd; it is a kernel function.
//
OSStatus SessionGetInfo(SecuritySessionId requestedSession,
    SecuritySessionId *sessionId,
    SessionAttributeBits *attributes)
{
    BEGIN_API
	CommonCriteria::AuditInfo session;
	if (requestedSession == callerSecuritySession)
		session.get();
	else
		session.get(requestedSession);
	if (sessionId)
		*sessionId = session.sessionId();
	if (attributes)
        *attributes = session.flags();
    END_API(CSSM)
}


//
// Create a new session.
// This no longer talks to securityd; it is a kernel function.
// Securityd will pick up the new session when we next talk to it.
//
OSStatus SessionCreate(SessionCreationFlags flags,
    SessionAttributeBits attributes)
{
    BEGIN_API

	// we don't support the session creation flags anymore
	if (flags)
		Syslog::warning("SessionCreate flags=0x%x unsupported (ignored)", flags);
	CommonCriteria::AuditInfo session;
	session.create(attributes);
        
	// retrieve the (new) session id and set it into the process environment
	session.get();
	char idString[80];
	snprintf(idString, sizeof(idString), "%x", session.sessionId());
	setenv("SECURITYSESSIONID", idString, 1);

    END_API(CSSM)
}


//
// Get and set the distinguished uid (optionally) associated with the session.
//
OSStatus SessionSetDistinguishedUser(SecuritySessionId session, uid_t user)
{
	BEGIN_API
	CommonCriteria::AuditInfo session;
	session.get();
	session.ai_auid = user;
	session.set();
	END_API(CSSM)
}


OSStatus SessionGetDistinguishedUser(SecuritySessionId session, uid_t *user)
{
    BEGIN_API
	CommonCriteria::AuditInfo session;
	session.get();
	Required(user) = session.uid();
    END_API(CSSM)
}

OSStatus _SessionSetUserPreferences(SecuritySessionId session);

void SessionUserPreferencesChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo)
{
	_SessionSetUserPreferences(uintptr_t(observer));
}

OSStatus _SessionSetUserPreferences(SecuritySessionId session)
{
    BEGIN_API
	CFStringRef appleLanguagesStr = CFSTR("AppleLanguages");
	CFStringRef controlTintStr = CFSTR("AppleAquaColorVariant");
	CFStringRef keyboardUIModeStr = CFSTR("AppleKeyboardUIMode");
	CFStringRef textDirectionStr = CFSTR("AppleTextDirection");
	CFStringRef hitoolboxAppIDStr = CFSTR("com.apple.HIToolbox");
	CFNotificationCenterRef center = CFNotificationCenterGetDistributedCenter();

	CFRef<CFMutableDictionaryRef> userPrefsDict(CFDictionaryCreateMutable(NULL, 10, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
	CFRef<CFMutableDictionaryRef> globalPrefsDict(CFDictionaryCreateMutable(NULL, 10, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
	
	if (!userPrefsDict || !globalPrefsDict)
		return errSessionValueNotSet;
	
	CFRef<CFArrayRef> appleLanguagesArray(static_cast<CFArrayRef>(CFPreferencesCopyAppValue(appleLanguagesStr, kCFPreferencesCurrentApplication)));
	if (appleLanguagesArray)
		CFDictionarySetValue(globalPrefsDict, appleLanguagesStr, appleLanguagesArray);
	
	CFRef<CFNumberRef> controlTintNumber(static_cast<CFNumberRef>(CFPreferencesCopyAppValue(controlTintStr, kCFPreferencesCurrentApplication)));
	if (controlTintNumber)
		CFDictionarySetValue(globalPrefsDict, controlTintStr, controlTintNumber);

	CFRef<CFNumberRef> keyboardUIModeNumber(static_cast<CFNumberRef>(CFPreferencesCopyAppValue(keyboardUIModeStr, kCFPreferencesCurrentApplication)));
	if (keyboardUIModeNumber)
		CFDictionarySetValue(globalPrefsDict, keyboardUIModeStr, keyboardUIModeNumber);

	CFRef<CFNumberRef> textDirectionNumber(static_cast<CFNumberRef>(CFPreferencesCopyAppValue(textDirectionStr, kCFPreferencesCurrentApplication)));
	if (textDirectionNumber)
		CFDictionarySetValue(globalPrefsDict, textDirectionStr, textDirectionNumber);
	
	if (CFDictionaryGetCount(globalPrefsDict) > 0)
		CFDictionarySetValue(userPrefsDict, kCFPreferencesAnyApplication, globalPrefsDict);

	CFPreferencesSynchronize(hitoolboxAppIDStr, kCFPreferencesCurrentUser, 
			kCFPreferencesCurrentHost);
	CFRef<CFDictionaryRef> hitoolboxPrefsDict(static_cast<CFDictionaryRef>(CFPreferencesCopyMultiple(NULL, hitoolboxAppIDStr, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)));
	if (hitoolboxPrefsDict) {
		CFDictionarySetValue(userPrefsDict, hitoolboxAppIDStr, hitoolboxPrefsDict);
		CFNotificationCenterPostNotification(center, CFSTR("com.apple.securityagent.InputPrefsChanged"), CFSTR("com.apple.loginwindow"), hitoolboxPrefsDict, true);
	}
	
	CFRef<CFDataRef> userPrefsData(CFPropertyListCreateXMLData(NULL, userPrefsDict));
	if (!userPrefsData)
		return errSessionValueNotSet;
	server().setSessionUserPrefs(session, CFDataGetLength(userPrefsData), CFDataGetBytePtr(userPrefsData));

    END_API(CSSM)
}

OSStatus SessionSetUserPreferences(SecuritySessionId session)
{
	OSStatus status = _SessionSetUserPreferences(session);
	if (noErr == status) {
		CFNotificationCenterRef center = CFNotificationCenterGetDistributedCenter();
		// We've succeeded in setting up a static set of prefs, now set up 
		CFNotificationCenterAddObserver(center, (void*)session, SessionUserPreferencesChanged, CFSTR("com.apple.Carbon.TISNotifySelectedKeyboardInputSourceChanged"), NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
		CFNotificationCenterAddObserver(center, (void*)session, SessionUserPreferencesChanged, CFSTR("com.apple.Carbon.TISNotifyEnabledKeyboardInputSourcesChanged"), NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
	}
	return status;
}


//
// Modify Authorization rules
//

// 
// AuthorizationRightGet 
// 
OSStatus AuthorizationRightGet(const char *rightName, CFDictionaryRef *rightDefinition)
{
	BEGIN_API;
	Required(rightName);
	CssmDataContainer definition(server().returnAllocator);

	server().authorizationdbGet(rightName, definition, server().returnAllocator);
	// convert rightDefinition to dictionary
	
	if (rightDefinition)
	{
		CFRef<CFDataRef> data(CFDataCreate(NULL, static_cast<UInt8 *>(definition.data()), definition.length()));
		if (!data)
			CssmError::throwMe(errAuthorizationInternal);
			
		CFRef<CFDictionaryRef> rightDict(static_cast<CFDictionaryRef>(CFPropertyListCreateFromXMLData(NULL, data, kCFPropertyListImmutable, NULL)));
		if (!rightDict 
			|| CFGetTypeID(rightDict) != CFDictionaryGetTypeID()) 
				CssmError::throwMe(errAuthorizationInternal);

		CFRetain(rightDict);
		*rightDefinition = rightDict;
	}

	END_API(CSSM);
}

//
// AuthorizationRightSet
//
OSStatus AuthorizationRightSet(AuthorizationRef authRef, 
	const char *rightName, CFTypeRef rightDefinition, 
	CFStringRef descriptionKey, CFBundleRef bundle, CFStringRef tableName)
{
	BEGIN_API;
	Required(rightName);
	AuthorizationBlob *auth = (AuthorizationBlob *)authRef;

	CFRef<CFMutableDictionaryRef> rightDefinitionDict;
	if (rightDefinition && (CFGetTypeID(rightDefinition) == CFStringGetTypeID()))
	{
		rightDefinitionDict = CFDictionaryCreateMutable(NULL, 10, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
		if (!rightDefinitionDict)
				CssmError::throwMe(errAuthorizationInternal);
		CFDictionarySetValue(rightDefinitionDict, CFSTR(kAuthorizationRightRule), rightDefinition);
	}
	else
		if (rightDefinition && (CFGetTypeID(rightDefinition) == CFDictionaryGetTypeID()))
		{
			rightDefinitionDict = CFDictionaryCreateMutableCopy(NULL, 0, static_cast<CFDictionaryRef>(rightDefinition));
			if (!rightDefinitionDict)
				CssmError::throwMe(errAuthorizationInternal);
		}
		else
			CssmError::throwMe(errAuthorizationDenied);

	if (rightDefinitionDict)
		CFRelease(rightDefinitionDict); // we just assigned things that were already retained
	
	if (descriptionKey)
	{
		CFRef<CFMutableDictionaryRef> localizedDescriptions(CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
		
		if (!localizedDescriptions)
			CssmError::throwMe(errAuthorizationInternal);
	
		// assigning to perform a retain on either
		CFRef<CFBundleRef> clientBundle; clientBundle = bundle ? bundle : CFBundleGetMainBundle(); 
		
		// looks like a list of CFStrings: English us_en etc.
		CFRef<CFArrayRef> localizations(CFBundleCopyBundleLocalizations(clientBundle));
	
		if (localizations)
		{
			// for every CFString in localizations do
			CFIndex locIndex, allLocs = CFArrayGetCount(localizations);
			for (locIndex = 0; locIndex < allLocs; locIndex++)
			{
				CFStringRef oneLocalization = static_cast<CFStringRef>(CFArrayGetValueAtIndex(localizations, locIndex));
				
				if (!oneLocalization)
					continue;
		
				// @@@ no way to get "Localized" and "strings" as constants?
				CFRef<CFURLRef> locURL(CFBundleCopyResourceURLForLocalization(clientBundle, tableName ? tableName :  CFSTR("Localizable"), CFSTR("strings"), NULL /*subDirName*/, oneLocalization));
				
				if (!locURL)
					continue;
				
				CFDataRef tableData = NULL;
				SInt32 errCode;
				CFStringRef errStr;
				CFPropertyListRef stringTable;
		
				CFURLCreateDataAndPropertiesFromResource(CFGetAllocator(clientBundle), locURL, &tableData, NULL, NULL, &errCode);
				
				if (errCode)
				{
					if (NULL != tableData) {
						CFRelease(tableData);
					}
					continue;
				}
		
				stringTable = CFPropertyListCreateFromXMLData(CFGetAllocator(clientBundle), tableData, kCFPropertyListImmutable, &errStr);
				if (errStr != NULL) {
					CFRelease(errStr);
					errStr = NULL;
				}
				CFRelease(tableData);
				
				CFStringRef value = static_cast<CFStringRef>(CFDictionaryGetValue(static_cast<CFDictionaryRef>(stringTable), descriptionKey));
				if (value == NULL || CFEqual(value, CFSTR(""))) {
					CFRelease(stringTable);
					continue;
				} else {
					// oneLocalization/value into our dictionary 
					CFDictionarySetValue(localizedDescriptions, oneLocalization, value);
					CFRelease(stringTable);
				}
			}
		}

		// add the description as the default localization into the dictionary
		CFDictionarySetValue(localizedDescriptions, CFSTR(""), descriptionKey);
		
		// stuff localization table into rule definition
		CFDictionarySetValue(rightDefinitionDict, CFSTR(kAuthorizationRuleParameterDefaultPrompt), localizedDescriptions);

	}
	
	// serialize cfdictionary with data into rightDefinitionXML
	CFRef<CFDataRef> rightDefinitionXML(CFPropertyListCreateXMLData(NULL, rightDefinitionDict));

	server().authorizationdbSet(Required(auth), rightName, CFDataGetLength(rightDefinitionXML), CFDataGetBytePtr(rightDefinitionXML));
		
    END_API(CSSM);
}

//
// AuthorizationRightRemove
//
OSStatus AuthorizationRightRemove(AuthorizationRef authRef, const char *rightName)
{
	BEGIN_API;
	Required(rightName);
	AuthorizationBlob *auth = (AuthorizationBlob *)authRef;
	server().authorizationdbRemove(Required(auth), rightName);
	END_API(CSSM);
}