#include <security_utilities/osxcode.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <errno.h>
#include <CoreFoundation/CFBundle.h>
#include <CoreFoundation/CFPriv.h>
#include <CoreFoundation/CFBundlePriv.h>
using namespace CodeSigning;
namespace Security {
void OSXCode::scanFile(const char *pathname, Signer::State &state)
{
int fd = open(pathname, O_RDONLY);
if (fd < 0)
UnixError::throwMe();
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
void *p = mmap(NULL, st.st_size, PROT_READ, MAP_FILE, fd, 0);
close(fd); if (p == MAP_FAILED)
UnixError::throwMe();
secdebug("codesign", "scanning file %s (%ld bytes)", pathname, long(st.st_size));
state.enumerateContents(p, st.st_size);
munmap(p, st.st_size);
}
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;
}
}
RefPointer<OSXCode> OSXCode::main()
{
Boolean isRealBundle;
string path = cfString(_CFBundleCopyMainBundleExecutableURL(&isRealBundle), true);
if (isRealBundle) {
const char *cpath = path.c_str();
if (const char *slash = strrchr(cpath, '/'))
if (const char *contents = strstr(cpath, "/Contents/MacOS/"))
if (contents + 15 == slash)
return new ApplicationBundle(path.substr(0, contents-cpath).c_str());
secdebug("bundle", "OSXCode::main(%s) not recognized as bundle (treating as tool)", cpath);
}
return new ExecutableTool(path.c_str());
}
RefPointer<OSXCode> OSXCode::at(const char *path)
{
struct stat st;
if (stat(path, &st))
UnixError::throwMe();
if ((st.st_mode & S_IFMT) == S_IFDIR) { return new GenericBundle(path);
} else {
if (const char *slash = strrchr(path, '/'))
if (const char *contents = strstr(path, "/Contents/MacOS/"))
if (contents + 15 == slash)
return new GenericBundle(string(path).substr(0, contents-path).c_str(), path);
return new ExecutableTool(path);
}
}
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();
}
string ExecutableTool::executablePath() const
{
return path();
}
GenericBundle::GenericBundle(const char *path, const char *execPath )
: mPath(path), mBundle(NULL)
{
if (execPath) mExecutablePath = execPath;
secdebug("bundle", "%p GenericBundle from path %s(%s)", this, path, executablePath().c_str());
}
GenericBundle::GenericBundle(CFBundleRef bundle, const char *root )
: mBundle(bundle)
{
assert(bundle);
CFRetain(bundle);
mPath = root ? root : cfString(CFBundleCopyBundleURL(mBundle), true);
secdebug("bundle", "%p GenericBundle from bundle %p(%s)", this, bundle, mPath.c_str());
}
GenericBundle::~GenericBundle()
{
if (mBundle)
CFRelease(mBundle);
}
string GenericBundle::executablePath() const
{
if (mExecutablePath.empty())
return mExecutablePath = cfString(CFBundleCopyExecutableURL(cfBundle()), true);
else
return mExecutablePath;
}
CFBundleRef GenericBundle::cfBundle() const
{
if (!mBundle) {
secdebug("bundle", "instantiating CFBundle for %s", mPath.c_str());
CFRef<CFURLRef> url = CFURLCreateFromFileSystemRepresentation(NULL,
(const UInt8 *)mPath.c_str(), mPath.length(), true);
if (!url || !(mBundle = CFBundleCreate(NULL, url)))
CFError::throwMe();
}
return mBundle;
}
CFTypeRef GenericBundle::infoPlistItem(const char *name) const
{
return CFBundleGetValueForInfoDictionaryKey(cfBundle(), CFTempString(name));
}
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)
UnixError::throwMe(EBADEXEC); void *function = CFBundleGetFunctionPointerForName(cfBundle(), cfName);
if (function == NULL)
UnixError::throwMe(EBADEXEC); return function;
}
string GenericBundle::canonicalPath() const
{
return path();
}
string GenericBundle::resource(const char *name, const char *type, const char *subdir)
{
return cfString(CFBundleCopyResourceURL(cfBundle(),
CFTempString(name), CFTempString(type), CFTempString(subdir)), true);
}
void GenericBundle::resources(vector<string> &paths, const char *type, const char *subdir)
{
CFRef<CFArrayRef> cfList = CFBundleCopyResourceURLsOfType(cfBundle(),
CFTempString(type), CFTempString(subdir));
UInt32 size = CFArrayGetCount(cfList);
paths.reserve(size);
for (UInt32 n = 0; n < size; n++)
paths.push_back(cfString(CFURLRef(CFArrayGetValueAtIndex(cfList, n)), false));
}
void LoadableBundle::load()
{
if (!CFBundleLoadExecutable(cfBundle()))
CFError::throwMe();
secdebug("bundle", "%p (%s) loaded", this, path().c_str());
}
void LoadableBundle::unload()
{
secdebug("bundle", "%p (%s) unloaded", this, path().c_str());
CFBundleUnloadExecutable(cfBundle());
}
bool LoadableBundle::isLoaded() const
{
return CFBundleIsExecutableLoaded(cfBundle());
}
}