authz.c   [plain text]


/*
 * Copyright (c) 2003-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@
 *
 *  authz.c
 */

#include <getopt.h>
#include <stdio.h>
#include <Security/AuthorizationPriv.h>

#include "authz.h"
#include "security.h"

// AEWP?

AuthorizationRef
read_auth_ref_from_stdin()
{
	AuthorizationRef auth_ref = NULL;
	AuthorizationExternalForm extform;
	size_t bytes_read;
	
	while (kAuthorizationExternalFormLength != (bytes_read = read(STDIN_FILENO, &extform, kAuthorizationExternalFormLength)))
	{
		if ((bytes_read == -1) && ((errno != EAGAIN) || (errno != EINTR)))
			break;
	}
	if (bytes_read != kAuthorizationExternalFormLength)
		fprintf(stderr, "ERR: Failed to read authref\n");
	else
		if (AuthorizationCreateFromExternalForm(&extform, &auth_ref))
			fprintf(stderr, "ERR: Failed to internalize authref\n");

	close(0);

	return auth_ref;
}

int
write_auth_ref_to_stdout(AuthorizationRef auth_ref)
{
	AuthorizationExternalForm extform;
	size_t bytes_written;
	
	if (AuthorizationMakeExternalForm(auth_ref, &extform))
		return -1;
		
	while (kAuthorizationExternalFormLength != (bytes_written = write(STDOUT_FILENO, &extform, kAuthorizationExternalFormLength)))
	{
		if ((bytes_written == -1) && ((errno != EAGAIN) || (errno != EINTR)))
			break;
	}
	
	if (bytes_written == kAuthorizationExternalFormLength)
		return 0;

	return -1;
}

void
write_dict_to_stdout(CFDictionaryRef dict)
{
	if (!dict)
		return;

	CFDataRef right_definition_xml = CFPropertyListCreateXMLData(NULL, dict);

	if (!right_definition_xml)
		return;

	write(STDOUT_FILENO, CFDataGetBytePtr(right_definition_xml), CFDataGetLength(right_definition_xml));
	CFRelease(right_definition_xml);
}

CFDictionaryRef
read_dict_from_stdin()
{
	int bytes_read = 0;
	uint8_t buffer[4096];
	CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault, 0);
	CFErrorRef err = NULL;

	if (!data)
		return NULL;

	while (bytes_read = read(STDIN_FILENO, (void *)buffer, sizeof(buffer)))
	{
		if (bytes_read == -1)
			break;
		else
			CFDataAppendBytes(data, buffer, bytes_read);
	}

	CFDictionaryRef right_dict = (CFDictionaryRef)CFPropertyListCreateWithData(kCFAllocatorDefault, data, kCFPropertyListImmutable, NULL, &err);
	CFRelease(data);

	if (NULL == right_dict) {
		CFShow(err);
		return NULL;
	}
		
	if (CFGetTypeID(right_dict) != CFDictionaryGetTypeID())
	{
		fprintf(stderr, "This is not a dictionary.\n");
		CFRelease(right_dict);
		return NULL;
	}
	return right_dict;
}

CFPropertyListRef
read_plist_from_file(CFStringRef filePath)
{
	CFTypeRef         property = NULL;
	CFPropertyListRef propertyList = NULL;
	CFURLRef          fileURL = NULL;
	CFErrorRef       errorString = NULL;
	CFDataRef         resourceData = NULL;
	Boolean           status = FALSE;
	SInt32            errorCode = -1;

	// Convert the path to a URL.
	fileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, filePath, kCFURLPOSIXPathStyle, false);
	if (NULL == fileURL) {
		goto bail;
	}
	property = CFURLCreatePropertyFromResource(kCFAllocatorDefault, fileURL, kCFURLFileExists, NULL);
	if (NULL == property) {
		goto bail;
	}
	status = CFBooleanGetValue(property);
	if (!status) {
		fprintf(stderr, "The file does not exist.\n");
		goto bail;
	}

	// Read the XML file.
	status = CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, fileURL, &resourceData, NULL, NULL, &errorCode);
	if (!status) {
		fprintf(stderr, "Error (%d) reading the file.\n", (int)errorCode);
		goto bail;
	}

	// Reconstitute the dictionary using the XML data.
	propertyList = CFPropertyListCreateWithData(kCFAllocatorDefault, resourceData, kCFPropertyListImmutable, NULL, &errorString);
	if (NULL == propertyList) {
		CFShow(errorString);
		goto bail;
	}

	// Some error checking.
	if (!CFPropertyListIsValid(propertyList, kCFPropertyListXMLFormat_v1_0) || CFGetTypeID(propertyList) != CFDictionaryGetTypeID()) {
		fprintf(stderr, "The file is invalid.\n");
		CFRelease(propertyList);
		propertyList = NULL;
		goto bail;
	}

bail:
	if (NULL != fileURL)
		CFRelease(fileURL);
	if (NULL != property)
		CFRelease(property);
	if (NULL != resourceData)
		CFRelease(resourceData);
	
	return propertyList;
}

Boolean
write_plist_to_file(CFPropertyListRef propertyList, CFStringRef filePath)
{
	CFTypeRef   property = NULL;
	CFURLRef	fileURL = NULL;
	CFDataRef	xmlData = NULL;
	Boolean		status = FALSE;
	SInt32		errorCode = -1;
	CFErrorRef	errorRef = NULL;

	// Convert the path to a URL.
	fileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, filePath, kCFURLPOSIXPathStyle, false);
	if (NULL == fileURL) {
		goto bail;
	}
	property = CFURLCreatePropertyFromResource(kCFAllocatorDefault, fileURL, kCFURLFileExists, NULL);
	if (NULL == property) {
		goto bail;
	}
	if (!CFBooleanGetValue(property)) {
		fprintf(stderr, "The file does not exist.\n");
		goto bail;
	}
	
	// Convert the property list into XML data.
	xmlData = CFPropertyListCreateData(kCFAllocatorDefault, propertyList, kCFPropertyListXMLFormat_v1_0, 0, &errorRef);
	if (errorRef) {
		fprintf(stderr, "The file could not be written.\n");
		goto bail;
	}	
	
	// Write the XML data to the file.
	if (!CFURLWriteDataAndPropertiesToResource(fileURL, xmlData, NULL, &errorCode)) {
		fprintf(stderr, "The file could not be written.\n");
		goto bail;
	}
	
	status = TRUE;
bail:
	if (NULL != xmlData)
		CFRelease(xmlData);
	if (NULL != fileURL)
		CFRelease(fileURL);

	return status;
}

static void merge_dictionaries(const void *key, const void *value, void *mergeDict)
{
	CFDictionarySetValue(mergeDict, key, value);
}

int
authorizationdb(int argc, char * const * argv)
{
	AuthorizationRef auth_ref = NULL;
	int ch;
    while ((ch = getopt(argc, argv, "i")) != -1)
	{
		switch  (ch)
		{
		case 'i':
			auth_ref = read_auth_ref_from_stdin();
			break;
		case '?':
		default:
			return 2;
		}
	}

	argc -= optind;
	argv += optind;

	if (argc == 0)
		return 2; // required right parameter(s)
	
	OSStatus status;
	
	if (argc > 1)
	{
		if (!auth_ref && AuthorizationCreate(NULL, NULL, 0, &auth_ref))
			return -1;

		if (!strcmp("read", argv[0]))
		{
			CFDictionaryRef right_definition;
			status = AuthorizationRightGet(argv[1], &right_definition);
			if (!status)
			{
				write_dict_to_stdout(right_definition);
				CFRelease(right_definition);
			}
		}
		else if (!strcmp("write", argv[0]))
		{
			if (argc == 2)
			{
				CFDictionaryRef right_definition = read_dict_from_stdin();
				if (!right_definition)
					return -1;
				status = AuthorizationRightSet(auth_ref, argv[1], right_definition, NULL, NULL, NULL);
				CFRelease(right_definition);
			}
			else if (argc == 3)
			{
				// argv[2] is shortcut string
				CFStringRef shortcut_definition = CFStringCreateWithCStringNoCopy(NULL, argv[2], kCFStringEncodingUTF8, kCFAllocatorNull);
				if (!shortcut_definition)
					return -1;
				status = AuthorizationRightSet(auth_ref, argv[1], shortcut_definition, NULL, NULL, NULL);
				CFRelease(shortcut_definition);
			}
			else
				return 2; // take one optional argument - no more

		}
		else if (!strcmp("remove", argv[0]))
		{
			status = AuthorizationRightRemove(auth_ref, argv[1]);
		}
		else if (!strcmp("merge", argv[0])) {
			status = 1;
			CFStringRef sourcePath = NULL;
			CFStringRef destPath = NULL;
			CFPropertyListRef sourcePlist = NULL;
			CFPropertyListRef destPlist = NULL;
			CFDictionaryRef sourceRights = NULL;
			CFDictionaryRef sourceRules = NULL;
			CFDictionaryRef destRights = NULL;
			CFDictionaryRef destRules = NULL;
			CFIndex rightsCount = 0;
			CFIndex rulesCount = 0;
			CFMutableDictionaryRef mergeRights = NULL;
			CFMutableDictionaryRef mergeRules = NULL;
			CFMutableDictionaryRef outDict = NULL;

			if (argc < 2 || argc > 3)
				return 2;

			if (!strcmp("-", argv[1])) {
				// Merging from <STDIN>.
				sourcePlist = read_dict_from_stdin();
			} else {
				sourcePath = CFStringCreateWithCString(kCFAllocatorDefault, argv[1], kCFStringEncodingUTF8);
				if (NULL == sourcePath) {
					goto bail;
				}
				sourcePlist = read_plist_from_file(sourcePath);
			}
			if (NULL == sourcePlist)
				goto bail;				
			if (argc == 2) {
				// Merging to /etc/authorization.
				destPath = CFStringCreateWithCString(kCFAllocatorDefault, "/etc/authorization", kCFStringEncodingUTF8);
			} else {
				destPath = CFStringCreateWithCString(kCFAllocatorDefault, argv[2], kCFStringEncodingUTF8);
			}
			if (NULL == destPath) {
				goto bail;
			}
			destPlist = read_plist_from_file(destPath);
			if (NULL == destPlist)
				goto bail;

			sourceRights = CFDictionaryGetValue(sourcePlist, CFSTR("rights"));
			sourceRules = CFDictionaryGetValue(sourcePlist, CFSTR("rules"));
			destRights = CFDictionaryGetValue(destPlist, CFSTR("rights"));
			destRules = CFDictionaryGetValue(destPlist, CFSTR("rules"));
			if (sourceRights)
				rightsCount += CFDictionaryGetCount(sourceRights);
			if (destRights)
				rightsCount += CFDictionaryGetCount(destRights);
			mergeRights = CFDictionaryCreateMutable(NULL, rightsCount, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
			if (NULL == mergeRights) {
				goto bail;
			}
			if (sourceRules)
				rulesCount += CFDictionaryGetCount(sourceRules);
			if (destRules)
				rulesCount += CFDictionaryGetCount(destRules);
			mergeRules = CFDictionaryCreateMutable(NULL, rulesCount, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
			if (NULL == mergeRules) {
				goto bail;
			}
			
			if (destRights)
				CFDictionaryApplyFunction(destRights, merge_dictionaries, mergeRights);
			if (destRules)
				CFDictionaryApplyFunction(destRules, merge_dictionaries, mergeRules);
			if (sourceRights)
				CFDictionaryApplyFunction(sourceRights, merge_dictionaries, mergeRights);
			if (sourceRules)
				CFDictionaryApplyFunction(sourceRules, merge_dictionaries, mergeRules);

			outDict = CFDictionaryCreateMutable(NULL, 3, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
			if (NULL == outDict) {
				goto bail;
			}
			if (CFDictionaryContainsKey(sourcePlist, CFSTR("comment")))
				CFDictionaryAddValue(outDict, CFSTR("comment"), CFDictionaryGetValue(sourcePlist, CFSTR("comment")));
			else if (CFDictionaryContainsKey(destPlist, CFSTR("comment")))
				CFDictionaryAddValue(outDict, CFSTR("comment"), CFDictionaryGetValue(destPlist, CFSTR("comment")));
			CFDictionaryAddValue(outDict, CFSTR("rights"), mergeRights);
			CFDictionaryAddValue(outDict, CFSTR("rules"), mergeRules);
			if (!write_plist_to_file(outDict, destPath))
				goto bail;

			status = noErr;
bail:
			if (sourcePath)
				CFRelease(sourcePath);
			if (destPath)
				CFRelease(destPath);
			if (sourcePlist)
				CFRelease(sourcePlist);
			if (destPlist)
				CFRelease(destPlist);
			if (outDict)
				CFRelease(outDict);
		}		
		else
			return 2;
	}
	else
		return 2;

	if (auth_ref)
		AuthorizationFree(auth_ref, 0);

	if (!do_quiet)
		fprintf(stderr, "%s (%d)\n", status ? "NO" : "YES", (int)status);
	
	return (status ? -1 : 0);
}

int
authorize(int argc, char * const *argv)
{
	int ch;
	OSStatus status;

	Boolean user_interaction_allowed = FALSE, extend_rights = TRUE; 
	Boolean partial_rights = FALSE, destroy_rights = FALSE;
	Boolean pre_authorize = FALSE, internalize = FALSE, externalize = FALSE;
	Boolean wait = FALSE, explicit_credentials = FALSE; 
	Boolean isolate_explicit_credentials = FALSE, least_privileged = FALSE;
	char *login = NULL;

    while ((ch = getopt(argc, argv, "ucC:EpdPieqwxl")) != -1)
	{
		switch  (ch)
		{
		case 'u':
			user_interaction_allowed = TRUE;
			break;
		case 'c':
			explicit_credentials = TRUE;
			break;
		case 'C':
			explicit_credentials = TRUE;
			login = optarg;
			break;
		case 'e':
			externalize = TRUE;
			break;
		case 'E':
			extend_rights = FALSE;
			break;
		case 'p':
			partial_rights = TRUE;
			break;
		case 'd':
			destroy_rights = TRUE;
			break;
		case 'P':
			pre_authorize = TRUE;
			break;
		case 'i':
			internalize = TRUE;
			break;
		case 'w':
			wait = TRUE;
			externalize = TRUE;
			break;
		case 'x':
			isolate_explicit_credentials = TRUE;
			break;
		case 'l':
			least_privileged = TRUE;
			break;
		case '?':
		default:
			return 2; /* @@@ Return 2 triggers usage message. */
		}
	}

	argc -= optind;
	argv += optind;

	if (argc == 0)
		return 2; // required right parameter(s)

// set up AuthorizationFlags
	AuthorizationFlags flags = kAuthorizationFlagDefaults |
		(user_interaction_allowed ? kAuthorizationFlagInteractionAllowed : 0) |
		(extend_rights ? kAuthorizationFlagExtendRights : 0) |
		(partial_rights ? kAuthorizationFlagPartialRights : 0) | 
		(pre_authorize ? kAuthorizationFlagPreAuthorize : 0) |
		(least_privileged ? kAuthorizationFlagLeastPrivileged : 0);

// set up AuthorizationRightSet
	AuthorizationItem rights[argc];
	memset(rights, '\0', argc * sizeof(AuthorizationItem));
	AuthorizationItemSet rightset = { argc, rights };
	while (argc > 0)
		rights[--argc].name = *argv++;

	AuthorizationRef auth_ref = NULL;

// internalize AuthorizationRef
	if (internalize)
	{
		auth_ref = read_auth_ref_from_stdin();
		if (!auth_ref)
			return 1;
	}

	if (!auth_ref && AuthorizationCreate(NULL, NULL, 
				(least_privileged ? kAuthorizationFlagLeastPrivileged : 0), 
				&auth_ref))
		return -1;

// prepare environment if needed
	AuthorizationEnvironment *envp = NULL;
	if (explicit_credentials) {
		if (!login)
			login = getlogin();
		char *pass = getpass("Password:");
		if (!(login && pass))
			return 1;
		static AuthorizationItem authenv[] = {
			{ kAuthorizationEnvironmentUsername },
			{ kAuthorizationEnvironmentPassword },
			{ kAuthorizationEnvironmentShared }			// optional (see below)
		};
		static AuthorizationEnvironment env = { 0, authenv };
		authenv[0].valueLength = strlen(login);
		authenv[0].value = login;
		authenv[1].valueLength = strlen(pass);
		authenv[1].value = pass;
		if (isolate_explicit_credentials)
			env.count = 2;		// do not send kAuthorizationEnvironmentShared
		else
			env.count = 3;		// do send kAuthorizationEnvironmentShared
		envp = &env;
	}

// perform Authorization
	AuthorizationRights *granted_rights = NULL;
	status = AuthorizationCopyRights(auth_ref, &rightset, envp, flags, &granted_rights);

// externalize AuthorizationRef
	if (externalize)
		write_auth_ref_to_stdout(auth_ref);
			
	if (!do_quiet)
		fprintf(stderr, "%s (%d) ", status ? "NO" : "YES", (int)status);

	if (!do_quiet && !status && granted_rights)
	{
		uint32_t index;
		fprintf(stderr, "{ %d: ", (int)granted_rights->count);
		for (index = 0; index < granted_rights->count; index++)
		{
			fprintf(stderr, "\"%s\"%s %c ", granted_rights->items[index].name, 
				(kAuthorizationFlagCanNotPreAuthorize & granted_rights->items[index].flags) ? " (cannot-preauthorize)" : "", 
				(index+1 != granted_rights->count) ? ',' : '}');
		}
		AuthorizationFreeItemSet(granted_rights);
	}

	if (!do_quiet)
		fprintf(stderr, "\n");

// wait for client to pick up AuthorizationRef
	if (externalize && wait)
		while (-1 != write(STDOUT_FILENO, NULL, 0))
			usleep(100);
		
// drop AuthorizationRef
	if (auth_ref)
		AuthorizationFree(auth_ref, destroy_rights ? kAuthorizationFlagDestroyRights : 0);
	
	return (status ? -1 : 0);
}


int
execute_with_privileges(int argc, char * const *argv)
{
	AuthorizationRef auth_ref = NULL;
	int ch;
    while ((ch = getopt(argc, argv, "i")) != -1)
	{
		switch  (ch)
		{
		case 'i':
			auth_ref = read_auth_ref_from_stdin();
			break;
		case '?':
		default:
			return 2;
		}
	}

	argc -= optind;
	argv += optind;

	if (argc == 0)
		return 2; // required tool parameter(s)
	
	OSStatus status;
	
	if (!auth_ref && AuthorizationCreate(NULL, NULL, 0, &auth_ref))
		return -1;

	FILE *communications_pipe = NULL;

	status = AuthorizationExecuteWithPrivileges(auth_ref,argv[0], 0, (argc > 1) ? &argv[1] : NULL, &communications_pipe);

	if (!do_quiet)
		fprintf(stderr, "%s (%d) ", status ? "NO" : "YES", (int)status);
	
	if (!status)
	{
		int bytes_read = 0;
		uint8_t buffer[4096];
		
		while (bytes_read = read(STDIN_FILENO, &buffer, sizeof(buffer)))
		{
			if ((bytes_read == -1) && ((errno != EAGAIN) || (errno != EINTR)))
				break;
			else
				while ((-1 == write(fileno(communications_pipe), buffer, bytes_read)) &&
						((errno == EAGAIN) || (errno == EINTR)))
							usleep(100);
		}
	}
	
	return (status ? -1 : 0);	
}