#include "resources.h"
#include "csutilities.h"
#include <security_utilities/unix++.h>
#include <security_utilities/debugging.h>
#include <Security/CSCommon.h>
#include <security_utilities/unix++.h>
#include <security_utilities/cfmunge.h>
namespace Security {
namespace CodeSigning {
ResourceBuilder::ResourceBuilder(const std::string &root, CFDictionaryRef rulesDict, CodeDirectory::HashAlgorithm hashType)
: mRoot(root), mHashType(hashType)
{
assert(!mRoot.empty());
if (mRoot.substr(mRoot.length()-2, 2) == "/.") mRoot = mRoot.substr(0, mRoot.length()-2); const char * paths[2] = { mRoot.c_str(), NULL };
mFTS = fts_open((char * const *)paths, FTS_PHYSICAL | FTS_COMFOLLOW | FTS_NOCHDIR, NULL);
if (!mFTS)
UnixError::throwMe();
mRawRules = rulesDict;
CFDictionary rules(rulesDict, errSecCSResourceRulesInvalid);
rules.apply(this, &ResourceBuilder::addRule);
}
ResourceBuilder::~ResourceBuilder()
{
for (Rules::iterator it = mRules.begin(); it != mRules.end(); ++it)
delete *it;
UnixPlusPlus::checkError(fts_close(mFTS));
}
void ResourceBuilder::addRule(CFTypeRef key, CFTypeRef value)
{
string pattern = cfString(key, errSecCSResourceRulesInvalid);
unsigned weight = 1;
uint32_t flags = 0;
if (CFGetTypeID(value) == CFBooleanGetTypeID()) {
if (value == kCFBooleanFalse)
flags |= omitted;
} else {
CFDictionary rule(value, errSecCSResourceRulesInvalid);
if (CFNumberRef weightRef = rule.get<CFNumberRef>("weight"))
weight = cfNumber<unsigned int>(weightRef);
if (CFBooleanRef omitRef = rule.get<CFBooleanRef>("omit"))
if (omitRef == kCFBooleanTrue)
flags |= omitted;
if (CFBooleanRef optRef = rule.get<CFBooleanRef>("optional"))
if (optRef == kCFBooleanTrue)
flags |= optional;
if (CFBooleanRef nestRef = rule.get<CFBooleanRef>("nested"))
if (nestRef == kCFBooleanTrue)
flags |= nested;
if (CFBooleanRef topRef = rule.get<CFBooleanRef>("top"))
if (topRef == kCFBooleanTrue)
flags |= top;
}
addRule(new Rule(pattern, weight, flags));
}
void ResourceBuilder::scan(Scanner next)
{
bool first = true;
while (FTSENT *ent = fts_read(mFTS)) {
const char *relpath = ent->fts_path + mRoot.size() + 1; switch (ent->fts_info) {
case FTS_F:
secdebug("rdirenum", "file %s", ent->fts_path);
if (Rule *rule = findRule(relpath))
if (!(rule->flags & (omitted | exclusion)))
next(ent, rule->flags, relpath, rule);
break;
case FTS_SL:
secdebug("rdirenum", "symlink %s", ent->fts_path);
if (Rule *rule = findRule(relpath))
if (!(rule->flags & (omitted | exclusion)))
next(ent, rule->flags & ~nested, relpath, rule);
break;
case FTS_D:
secdebug("rdirenum", "entering %s", ent->fts_path);
if (!first) { if (Rule *rule = findRule(relpath)) {
if (rule->flags & nested) {
if (strchr(ent->fts_name, '.')) { next(ent, rule->flags, relpath, rule);
fts_set(mFTS, ent, FTS_SKIP);
}
} else if (rule->flags & exclusion) { fts_set(mFTS, ent, FTS_SKIP);
}
}
}
first = false;
break;
case FTS_DP:
secdebug("rdirenum", "leaving %s", ent->fts_path);
break;
default:
secdebug("rdirenum", "type %d (errno %d): %s",
ent->fts_info, ent->fts_errno, ent->fts_path);
break;
}
}
}
bool ResourceBuilder::includes(string path) const
{
if (Rule *rule = findRule(path))
return !(rule->flags & (omitted | exclusion));
else
return false;
}
ResourceBuilder::Rule *ResourceBuilder::findRule(string path) const
{
Rule *bestRule = NULL;
secdebug("rscan", "test %s", path.c_str());
for (Rules::const_iterator it = mRules.begin(); it != mRules.end(); ++it) {
Rule *rule = *it;
secdebug("rscan", "try %s", rule->source.c_str());
if (rule->match(path.c_str())) {
secdebug("rscan", "match");
if (rule->flags & exclusion) {
secdebug("rscan", "excluded");
return rule;
}
if (!bestRule || rule->weight > bestRule->weight)
bestRule = rule;
}
}
secdebug("rscan", "choosing %s (%d,0x%x)",
bestRule ? bestRule->source.c_str() : "NOTHING",
bestRule ? bestRule->weight : 0,
bestRule ? bestRule->flags : 0);
return bestRule;
}
CFDataRef ResourceBuilder::hashFile(const char *path) const
{
UnixPlusPlus::AutoFileDesc fd(path);
fd.fcntl(F_NOCACHE, true); MakeHash<ResourceBuilder> hasher(this);
hashFileData(fd, hasher.get());
Hashing::Byte digest[hasher->digestLength()];
hasher->finish(digest);
return CFDataCreate(NULL, digest, sizeof(digest));
}
ResourceBuilder::Rule::Rule(const std::string &pattern, unsigned w, uint32_t f)
: weight(w), flags(f), source(pattern)
{
if (::regcomp(this, pattern.c_str(), REG_EXTENDED | REG_NOSUB)) MacOSError::throwMe(errSecCSResourceRulesInvalid);
secdebug("csresource", "%p rule %s added (weight %d, flags 0x%x)",
this, pattern.c_str(), w, f);
}
ResourceBuilder::Rule::~Rule()
{
::regfree(this);
}
bool ResourceBuilder::Rule::match(const char *s) const
{
switch (::regexec(this, s, 0, NULL, 0)) {
case 0:
return true;
case REG_NOMATCH:
return false;
default:
MacOSError::throwMe(errSecCSResourceRulesInvalid);
}
}
std::string ResourceBuilder::escapeRE(const std::string &s)
{
string r;
for (string::const_iterator it = s.begin(); it != s.end(); ++it) {
char c = *it;
if (strchr("\\[]{}().+*", c))
r.push_back('\\');
r.push_back(c);
}
return r;
}
ResourceSeal::ResourceSeal(CFTypeRef it)
: mDict(NULL), mHash(NULL), mRequirement(NULL), mLink(NULL), mFlags(0)
{
if (it == NULL)
MacOSError::throwMe(errSecCSResourcesInvalid);
if (CFGetTypeID(it) == CFDataGetTypeID()) {
mHash = CFDataRef(it);
} else {
int optional = 0;
mDict = CFDictionaryRef(it);
bool err;
if (CFDictionaryGetValue(mDict, CFSTR("requirement")))
err = !cfscan(mDict, "{requirement=%SO,?optional=%B}", &mRequirement, &optional);
else if (CFDictionaryGetValue(mDict, CFSTR("symlink")))
err = !cfscan(mDict, "{symlink=%SO,?optional=%B}", &mLink, &optional);
else
err = !cfscan(mDict, "{hash=%XO,?optional=%B}", &mHash, &optional);
if (err)
MacOSError::throwMe(errSecCSResourcesInvalid);
if (optional)
mFlags |= ResourceBuilder::optional;
if (mRequirement)
mFlags |= ResourceBuilder::nested;
}
}
} }