#include "bundlediskrep.h"
#include "filediskrep.h"
#include <CoreFoundation/CFURLAccess.h>
#include <CoreFoundation/CFBundlePriv.h>
#include <security_utilities/cfmunge.h>
#include <copyfile.h>
#include <fts.h>
namespace Security {
namespace CodeSigning {
using namespace UnixPlusPlus;
static std::string findDistFile(const std::string &directory);
BundleDiskRep::BundleDiskRep(const char *path, const Context *ctx)
: mBundle(CFBundleCreate(NULL, CFTempURL(path)))
{
if (!mBundle)
MacOSError::throwMe(errSecCSBadBundleFormat);
setup(ctx);
CODESIGN_DISKREP_CREATE_BUNDLE_PATH(this, (char*)path, (void*)ctx, mExecRep);
}
BundleDiskRep::BundleDiskRep(CFBundleRef ref, const Context *ctx)
{
mBundle = ref; setup(ctx);
CODESIGN_DISKREP_CREATE_BUNDLE_REF(this, ref, (void*)ctx, mExecRep);
}
void BundleDiskRep::setup(const Context *ctx)
{
mInstallerPackage = false;
string version = resourcesRootPath()
+ "/Versions/"
+ ((ctx && ctx->version) ? ctx->version : "Current")
+ "/.";
if (::access(version.c_str(), F_OK) == 0) { if (CFBundleRef versionBundle = CFBundleCreate(NULL, CFTempURL(version)))
mBundle.take(versionBundle); else
MacOSError::throwMe(errSecCSStaticCodeNotFound);
} else {
if (ctx && ctx->version) MacOSError::throwMe(errSecCSStaticCodeNotFound);
}
CFDictionaryRef infoDict = CFBundleGetInfoDictionary(mBundle);
assert(infoDict); CFTypeRef mainHTML = CFDictionaryGetValue(infoDict, CFSTR("MainHTML"));
CFTypeRef packageVersion = CFDictionaryGetValue(infoDict, CFSTR("IFMajorVersion"));
if (CFRef<CFURLRef> mainExec = CFBundleCopyExecutableURL(mBundle)) if (mainHTML == NULL) { mMainExecutableURL = mainExec;
mExecRep = DiskRep::bestFileGuess(this->mainExecutablePath(), ctx);
mFormat = "bundle with " + mExecRep->format();
return;
}
if (mainHTML) {
if (CFGetTypeID(mainHTML) != CFStringGetTypeID())
MacOSError::throwMe(errSecCSBadBundleFormat);
mMainExecutableURL.take(makeCFURL(cfString(CFStringRef(mainHTML)), false,
CFRef<CFURLRef>(CFBundleCopySupportFilesDirectoryURL(mBundle))));
if (!mMainExecutableURL)
MacOSError::throwMe(errSecCSBadBundleFormat);
mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
mFormat = "widget bundle";
return;
}
if (CFRef<CFURLRef> infoURL = _CFBundleCopyInfoPlistURL(mBundle)) {
if ((mMainExecutableURL = _CFBundleCopyInfoPlistURL(mBundle))) {
mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
if (packageVersion) {
mInstallerPackage = true;
mFormat = "installer package bundle";
} else {
mFormat = "bundle";
}
return;
}
}
std::string distFile = findDistFile(this->resourcesRootPath());
if (!distFile.empty()) {
mMainExecutableURL = makeCFURL(distFile);
mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
mInstallerPackage = true;
mFormat = "installer package bundle";
return;
}
MacOSError::throwMe(errSecCSBadBundleFormat);
}
static std::string findDistFile(const std::string &directory)
{
std::string found;
char *paths[] = {(char *)directory.c_str(), NULL};
FTS *fts = fts_open(paths, FTS_PHYSICAL | FTS_NOCHDIR | FTS_NOSTAT, NULL);
bool root = true;
while (FTSENT *ent = fts_read(fts)) {
switch (ent->fts_info) {
case FTS_F:
case FTS_NSOK:
if (!strcmp(ent->fts_path + ent->fts_pathlen - 5, ".dist")) { if (found.empty()) found = ent->fts_path;
else MacOSError::throwMe(errSecCSBadBundleFormat);
}
break;
case FTS_D:
if (!root)
fts_set(fts, ent, FTS_SKIP); root = false;
break;
default:
break;
}
}
fts_close(fts);
return found;
}
string BundleDiskRep::metaPath(const char *name)
{
if (mMetaPath.empty()) {
string support = cfStringRelease(CFBundleCopySupportFilesDirectoryURL(mBundle));
mMetaPath = support + "/" BUNDLEDISKREP_DIRECTORY;
if (::access(mMetaPath.c_str(), F_OK) == 0) {
mMetaExists = true;
} else {
mMetaPath = support;
mMetaExists = false;
}
}
return mMetaPath + "/" + name;
}
void BundleDiskRep::createMeta()
{
string meta = metaPath(BUNDLEDISKREP_DIRECTORY);
if (!mMetaExists) {
if (::mkdir(meta.c_str(), 0755) == 0) {
copyfile(cfString(canonicalPath(), true).c_str(), meta.c_str(), NULL, COPYFILE_SECURITY);
mMetaPath = meta;
mMetaExists = true;
} else if (errno != EEXIST)
UnixError::throwMe();
}
}
CFDataRef BundleDiskRep::component(CodeDirectory::SpecialSlot slot)
{
switch (slot) {
case cdInfoSlot:
if (CFRef<CFURLRef> info = _CFBundleCopyInfoPlistURL(mBundle))
return cfLoadFile(info);
else
return NULL;
default:
if (CFDataRef data = mExecRep->component(slot))
return data;
case cdResourceDirSlot:
if (const char *name = CodeDirectory::canonicalSlotName(slot))
return metaData(name);
else
return NULL;
}
}
CFDataRef BundleDiskRep::identification()
{
return mExecRep->identification();
}
CFURLRef BundleDiskRep::canonicalPath()
{
return CFBundleCopyBundleURL(mBundle);
}
string BundleDiskRep::mainExecutablePath()
{
return cfString(mMainExecutableURL);
}
string BundleDiskRep::resourcesRootPath()
{
return cfStringRelease(CFBundleCopySupportFilesDirectoryURL(mBundle));
}
void BundleDiskRep::adjustResources(ResourceBuilder &builder)
{
builder.addExclusion("^" BUNDLEDISKREP_DIRECTORY "/");
builder.addExclusion("^" STORE_RECEIPT_DIRECTORY "/");
string resources = resourcesRootPath();
string executable = mainExecutablePath();
if (!executable.compare(0, resources.length(), resources, 0, resources.length())) builder.addExclusion(string("^")
+ ResourceBuilder::escapeRE(executable.substr(resources.length() + 1)) + "$");
}
Universal *BundleDiskRep::mainExecutableImage()
{
return mExecRep->mainExecutableImage();
}
size_t BundleDiskRep::signingBase()
{
return mExecRep->signingBase();
}
size_t BundleDiskRep::signingLimit()
{
return mExecRep->signingLimit();
}
string BundleDiskRep::format()
{
return mFormat;
}
CFArrayRef BundleDiskRep::modifiedFiles()
{
CFMutableArrayRef files = CFArrayCreateMutableCopy(NULL, 0, mExecRep->modifiedFiles());
checkModifiedFile(files, cdCodeDirectorySlot);
checkModifiedFile(files, cdSignatureSlot);
checkModifiedFile(files, cdResourceDirSlot);
checkModifiedFile(files, cdEntitlementSlot);
return files;
}
void BundleDiskRep::checkModifiedFile(CFMutableArrayRef files, CodeDirectory::SpecialSlot slot)
{
if (CFDataRef data = mExecRep->component(slot)) CFRelease(data);
else if (const char *resourceName = CodeDirectory::canonicalSlotName(slot)) {
string file = metaPath(resourceName);
if (::access(file.c_str(), F_OK) == 0)
CFArrayAppendValue(files, CFTempURL(file));
}
}
FileDesc &BundleDiskRep::fd()
{
return mExecRep->fd();
}
void BundleDiskRep::flush()
{
mExecRep->flush();
}
string BundleDiskRep::recommendedIdentifier(const SigningContext &)
{
if (CFStringRef identifier = CFBundleGetIdentifier(mBundle))
return cfString(identifier);
if (CFDictionaryRef infoDict = CFBundleGetInfoDictionary(mBundle))
if (CFStringRef identifier = CFStringRef(CFDictionaryGetValue(infoDict, kCFBundleNameKey)))
return cfString(identifier);
return canonicalIdentifier(cfString(this->canonicalPath()));
}
CFDictionaryRef BundleDiskRep::defaultResourceRules(const SigningContext &)
{
string rbase = this->resourcesRootPath();
if (rbase.substr(rbase.length()-2, 2) == "/.") rbase = rbase.substr(0, rbase.length()-2); string resources = cfStringRelease(CFBundleCopyResourcesDirectoryURL(mBundle));
if (resources == rbase)
resources = "";
else if (resources.compare(0, rbase.length(), rbase, 0, rbase.length()) != 0) MacOSError::throwMe(errSecCSBadBundleFormat);
else
resources = resources.substr(rbase.length() + 1) + "/";
if (mInstallerPackage)
return cfmake<CFDictionaryRef>("{rules={"
"'^.*' = #T" "%s = {optional=#T, weight=1000}" "'^.*/.*\\.pkg/' = {omit=#T, weight=10000}" "}}",
(string("^") + resources + ".*\\.lproj/").c_str()
);
return cfmake<CFDictionaryRef>("{rules={"
"'^version.plist$' = #T" "%s = #T" "%s = {optional=#T, weight=1000}" "%s = {omit=#T, weight=1100}" "}}",
(string("^") + resources).c_str(),
(string("^") + resources + ".*\\.lproj/").c_str(),
(string("^") + resources + ".*\\.lproj/locversion.plist$").c_str()
);
}
const Requirements *BundleDiskRep::defaultRequirements(const Architecture *arch, const SigningContext &ctx)
{
return mExecRep->defaultRequirements(arch, ctx);
}
size_t BundleDiskRep::pageSize(const SigningContext &ctx)
{
return mExecRep->pageSize(ctx);
}
DiskRep::Writer *BundleDiskRep::writer()
{
return new Writer(this);
}
BundleDiskRep::Writer::Writer(BundleDiskRep *r)
: rep(r), mMadeMetaDirectory(false)
{
execWriter = rep->mExecRep->writer();
}
void BundleDiskRep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef data)
{
switch (slot) {
default:
if (!execWriter->attribute(writerLastResort)) return execWriter->component(slot, data); case cdResourceDirSlot:
if (const char *name = CodeDirectory::canonicalSlotName(slot)) {
rep->createMeta();
string path = rep->metaPath(name);
AutoFileDesc fd(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
fd.writeAll(CFDataGetBytePtr(data), CFDataGetLength(data));
} else
MacOSError::throwMe(errSecCSBadBundleFormat);
}
}
void BundleDiskRep::Writer::remove()
{
execWriter->remove();
for (CodeDirectory::SpecialSlot slot = 0; slot < cdSlotCount; slot++)
remove(slot);
remove(cdSignatureSlot);
}
void BundleDiskRep::Writer::remove(CodeDirectory::SpecialSlot slot)
{
if (const char *name = CodeDirectory::canonicalSlotName(slot))
if (::unlink(rep->metaPath(name).c_str()))
switch (errno) {
case ENOENT: break;
default:
UnixError::throwMe();
}
}
void BundleDiskRep::Writer::flush()
{
execWriter->flush();
}
} }