/*
 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
 * 
 * The contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License. Please obtain
 * a copy of the License at http://www.apple.com/publicsource and read it before
 * using this file.
 * 
 * This 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.
 */


//
// osxsigning - MacOS X's standard signable objects.
//
#include <Security/osxsigning.h>
#include <Security/cfutilities.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <errno.h>
#include <CoreFoundation/CFBundle.h>


namespace Security
{

namespace CodeSigning
{

//
// Enumerate a single file on disk.
//
void OSXCode::scanFile(const char *pathname, Signer::State &state)
{
	// open the file (well, try)
	int fd = open(pathname, O_RDONLY);
	if (fd < 0)
		UnixError::throwMe();
	
	// how big is it?
	struct stat st;
	if (fstat(fd, &st)) {
		close(fd);
		UnixError::throwMe();
	}
#if defined(LIMITED_SIGNING)
	if (st.st_size >= 0x4000)
		st.st_size = 0x4000;
#endif
	
	// map it
	void *p = mmap(NULL, st.st_size, PROT_READ, MAP_FILE, fd, 0);
	close(fd);	// done with this either way
	if (p == MAP_FAILED)
		UnixError::throwMe();
	
	// scan it
	debug("codesign", "scanning file %s (%ld bytes)", pathname, long(st.st_size));
	state.enumerateContents(p, st.st_size);
	
	// unmap it (ignore error)
	munmap(p, st.st_size);
}


//
// Use prefix encoding for externalizing OSXCode objects
//
OSXCode *OSXCode::decode(const char *extForm)
{
	if (!extForm || !extForm[0] || extForm[1] != ':')
		return NULL;
	switch (extForm[0]) {
	case 't':
		return new ExecutableTool(extForm+2);
	case 'b':
		return new GenericBundle(extForm+2);
	default:
		return NULL;
	}
}


//
// Produce a Signable for the currently running application
//
OSXCode *OSXCode::main()
{
	//@@@ cache the main bundle?
	if (CFBundleRef mainBundle = CFBundleGetMainBundle()) {
        CFRef<CFURLRef> base = CFBundleCopyBundleURL(mainBundle);
        CFRef<CFURLRef> resources(CFBundleCopyResourcesDirectoryURL(mainBundle));
        if (base && resources && !CFEqual(resources, base)) {
			// assume this is a real bundle
			return new ApplicationBundle(getPath(CFBundleCopyBundleURL(mainBundle)).c_str());
        }

		// too weird; assume this is a single-file "tool" executable
		return new ExecutableTool(getPath(CFBundleCopyExecutableURL(mainBundle)).c_str());
	}
	// CF gives no error indications...
	CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
}


// Note: The public CFURLCopyFileSystemPath fails to resolve relative URLs as
// produced by CFURL methods. We need to call an internal(!) method of CF to get
// the full path.
extern "C" CFStringRef CFURLCreateStringWithFileSystemPath(CFAllocatorRef allocator,
	CFURLRef anURL, CFURLPathStyle fsType, Boolean resolveAgainstBase);

string OSXCode::getPath(CFURLRef url)
{
    if (url == NULL)
        CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);	// source CF call failed

	CFRef<CFStringRef> str(CFURLCreateStringWithFileSystemPath(NULL, 
		url, kCFURLPOSIXPathStyle, true));
	CFRelease(url);
	if (str) {
		char path[PATH_MAX];
		if (CFStringGetCString(str, path, PATH_MAX, kCFStringEncodingUTF8))
			return path;
	}
	// no error indications from CF...
	CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
}


//
// Produce a Signable for whatever is at a given path.
// This tries to guess at the type of Signable to be used.
// If you *know*, just create the suitable subclass directly.
//
OSXCode *OSXCode::at(const char *path)
{
	struct stat st;
	if (stat(path, &st))
		UnixError::throwMe();
	if ((st.st_mode & S_IFMT) == S_IFDIR) {	// directory - assume bundle
		return new GenericBundle(path);
	} else {								// not directory - assume tool
		return new ExecutableTool(path);
	}
}


//
// Executable Tools
//
void ExecutableTool::scanContents(Signer::State &state) const
{
	scanFile(mPath.c_str(), state);
}

string ExecutableTool::encode() const
{
	return "t:" + mPath;
}

string ExecutableTool::canonicalPath() const
{
	return path();
}


//
// Generic Bundles
//
GenericBundle::GenericBundle(const char *path) : mPath(path)
{
	CFRef<CFURLRef> url(CFURLCreateFromFileSystemRepresentation(NULL, 
		(const UInt8 *)path, strlen(path), true));
	if (!url || !(mBundle = CFBundleCreate(NULL, url)))
        CssmError::throwMe(CSSMERR_CSSM_ADDIN_LOAD_FAILED);
}

GenericBundle::~GenericBundle()
{
    CFRelease(mBundle);
}


void GenericBundle::scanContents(Signer::State &state) const
{
	scanFile(executablePath().c_str(), state);
}

string GenericBundle::encode() const
{
	return "b:" + mPath;
}

void *GenericBundle::lookupSymbol(const char *name)
{
    CFRef<CFStringRef> cfName(CFStringCreateWithCString(NULL, name,
                                                        kCFStringEncodingMacRoman));
    if (!cfName)
        CssmError::throwMe(CSSM_ERRCODE_UNKNOWN_FORMAT);
    void *function = CFBundleGetFunctionPointerForName(mBundle, cfName);
    if (function == NULL)
        CssmError::throwMe(CSSM_ERRCODE_UNKNOWN_FORMAT);
    return function;
}

string GenericBundle::canonicalPath() const
{
	return path();
}


//
// Load management for a loadable bundle
//
void LoadableBundle::load()
{
	if (!CFBundleLoadExecutable(mBundle))
		CssmError::throwMe(CSSMERR_CSSM_ADDIN_LOAD_FAILED);
    IFDEBUG(debug("bundle", "%p (%s) loaded", this, path().c_str()));
}

void LoadableBundle::unload()
{
    IFDEBUG(debug("bundle", "%p (%s) unloaded", this, path().c_str()));
	CFBundleUnloadExecutable(mBundle);
}

bool LoadableBundle::isLoaded() const
{
	return CFBundleIsExecutableLoaded(mBundle);
}


}; // end namespace CodeSigning

} // end namespace Security
