//
// Executable code.
// Read from disk, evaluate properly, cache as indicated.
//
void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result, bool handleUnsigned)
{
	// not really a Gatekeeper function... but reject all "hard quarantined" files because they were made from sandboxed sources without download privilege
	FileQuarantine qtn(cfString(path).c_str());
	if (qtn.flag(QTN_FLAG_HARD))
		MacOSError::throwMe(errSecCSFileHardQuarantined);
	
	CFCopyRef<SecStaticCodeRef> code;
	MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
	
	SecCSFlags validationFlags = kSecCSEnforceRevocationChecks | kSecCSCheckAllArchitectures;
	if (!(flags & kSecAssessmentFlagAllowWeak))
		validationFlags |= kSecCSStrictValidate;
	adjustValidation(code);
	
	// deal with a very special case (broken 10.6/10.7 Applet bundles)
	OSStatus rc = SecStaticCodeCheckValidity(code, validationFlags | kSecCSBasicValidateOnly, NULL);
	if (rc == errSecCSSignatureFailed) {
		if (!codeInvalidityExceptions(code, result)) {	// invalidly signed, no exceptions -> error
			if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
				SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, false);
			MacOSError::throwMe(rc);
		}
		// recognized exception - treat as unsigned
		if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
			SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, true);
		rc = errSecCSUnsigned;
	}

	// ad-hoc sign unsigned code
	if (rc == errSecCSUnsigned && handleUnsigned && (!overrideAssessment(flags) || SYSPOLICY_RECORDER_MODE_ENABLED())) {
		if (temporarySigning(code, type, path, 0)) {
			rc = errSecSuccess;		// clear unsigned; we are now well-signed
			validationFlags |= kSecCSBasicValidateOnly;	// no need to re-validate deep contents
		}
	}
	
	// prepare for deep traversal of (hopefully) good signatures
	SecAssessmentFeedback feedback = SecAssessmentFeedback(CFDictionaryGetValue(context, kSecAssessmentContextKeyFeedback));
	MacOSError::check(SecStaticCodeSetCallback(code, kSecCSDefaultFlags, NULL, ^CFTypeRef (SecStaticCodeRef item, CFStringRef cfStage, CFDictionaryRef info) {
		string stage = cfString(cfStage);
		if (stage == "prepared") {
			if (!CFEqual(item, code))	// genuine nested (not top) code
				adjustValidation(item);
		} else if (stage == "progress") {
			if (feedback && CFEqual(item, code)) {	// top level progress
				bool proceed = feedback(kSecAssessmentFeedbackProgress, info);
				if (!proceed)
					SecStaticCodeCancelValidation(code, kSecCSDefaultFlags);
			}
		} else if (stage == "validated") {
			SecStaticCodeSetCallback(item, kSecCSDefaultFlags, NULL, NULL);		// clear callback to avoid unwanted recursion
			evaluateCodeItem(item, path, type, flags, item != code, result);
			if (CFTypeRef verdict = CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict))
				if (CFEqual(verdict, kCFBooleanFalse))
					return makeCFNumber(OSStatus(errSecCSVetoed));	// (signal nested-code policy failure, picked up below)
		}
		return NULL;
	}));
//
// Executable code.
// Read from disk, evaluate properly, cache as indicated. The whole thing, so far.
//
void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result,
	bool handleUnsignedCode /* = true */)
{
	FileQuarantine qtn(cfString(path).c_str());
	if (qtn.flag(QTN_FLAG_HARD))
		MacOSError::throwMe(errSecCSFileHardQuarantined);
	
	CFRef<SecStaticCodeRef> code;
	MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
	OSStatus rc = noErr;	// last validation error
	
	const SecCSFlags validationFlags = kSecCSEnforceRevocationChecks;
	
	WhitelistPrescreen whitelistScreen(code); // pre-screening filter for whitelist pre-screening (only)

	SQLite::Statement query(*this,
		"SELECT allow, requirement, id, label, expires, flags, disabled, filter_unsigned, remarks FROM scan_authority"
		" WHERE type = :type"
		" ORDER BY priority DESC;");
	query.bind(":type").integer(type);
	SQLite3::int64 latentID = 0;		// first (highest priority) disabled matching ID
	std::string latentLabel;			// ... and associated label, if any
	while (query.nextRow()) {
		bool allow = int(query[0]);
		const char *reqString = query[1];
		SQLite3::int64 id = query[2];
		const char *label = query[3];
		double expires = query[4];
		sqlite3_int64 ruleFlags = query[5];
		SQLite3::int64 disabled = query[6];
		const char *filter = query[7];
		const char *remarks = query[8];
		
		CFRef<SecRequirementRef> requirement;
		MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref()));
		rc = SecStaticCodeCheckValidity(code, validationFlags, requirement);
		
		// ad-hoc sign unsigned code, skip of Gatekeeper is off or the rule is disabled; but always do it for whitelist recording
		if (rc == errSecCSUnsigned && handleUnsignedCode && (!(disabled || overrideAssessment()) || SYSPOLICY_RECORDER_MODE_ENABLED())) {
			if (!SYSPOLICY_RECORDER_MODE_ENABLED()) {
				// apply whitelist pre-screening to speed things up for non-matches
				if (ruleFlags & kAuthorityFlagDefault)	// can't ever match standard rules with unsigned code
					continue;
				if (whitelistScreen.reject(filter, remarks))	// apply whitelist pre-filter
					continue;
			}
			try {
				// ad-hoc sign the code and attach the signature
				CFRef<CFDataRef> signature = CFDataCreateMutable(NULL, 0);
				CFTemp<CFDictionaryRef> arguments("{%O=%O, %O=#N}", kSecCodeSignerDetached, signature.get(), kSecCodeSignerIdentity);
				CFRef<SecCodeSignerRef> signer;
				MacOSError::check(SecCodeSignerCreate(arguments, kSecCSDefaultFlags, &signer.aref()));
				MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags));
				MacOSError::check(SecCodeSetDetachedSignature(code, signature, kSecCSDefaultFlags));
								
				// if we're in GKE recording mode, save that signature and report its location
				if (SYSPOLICY_RECORDER_MODE_ENABLED()) {
					int status = recorder_code_unable;	// ephemeral signature (not recorded)
					if (geteuid() == 0) {
						CFRef<CFUUIDRef> uuid = CFUUIDCreate(NULL);
						std::string sigfile = RECORDER_DIR + cfStringRelease(CFUUIDCreateString(NULL, uuid)) + ".tsig";
						try {
							UnixPlusPlus::AutoFileDesc fd(sigfile, O_WRONLY | O_CREAT);
							fd.write(CFDataGetBytePtr(signature), CFDataGetLength(signature));
							status = recorder_code_adhoc;	// recorded signature
							SYSPOLICY_RECORDER_MODE_ADHOC_PATH(cfString(path).c_str(), type, sigfile.c_str());
						} catch (...) { }
					}

					// now report the D probe itself
					CFRef<CFDictionaryRef> info;
					MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
					CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
					SYSPOLICY_RECORDER_MODE(cfString(path).c_str(), type, "",
						cdhash ? CFDataGetBytePtr(cdhash) : NULL, status);
				}
				
				// rerun the validation to update state
				rc = SecStaticCodeCheckValidity(code, validationFlags | kSecCSBasicValidateOnly, requirement);
			} catch (...) { }
		}

		switch (rc) {
		case noErr: // well signed and satisfies requirement...
			break;	// ... continue below
		case errSecCSSignatureFailed:
			if (!codeInvalidityExceptions(code, result)) {
				if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
					SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, false);
				MacOSError::throwMe(rc);
			}
			if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
				SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, true);
			// treat as unsigned to fix problems in the field
		case errSecCSUnsigned:
			if (handleUnsignedCode) {
				cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
				addAuthority(result, "no usable signature");
			}
			return;
		case errSecCSReqFailed: // requirement missed, but otherwise okay
			continue;
		default: // broken in some way; all tests will fail like this so bail out
			MacOSError::throwMe(rc);
		}
		if (disabled) {
			if (latentID == 0) {
				latentID = id;
				if (label)
					latentLabel = label;
			}
			continue;	// the loop
		}
	
		CFRef<CFDictionaryRef> info;	// as needed
		if (flags & kSecAssessmentFlagRequestOrigin) {
			if (!info)
				MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
			if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates)))
				setOrigin(chain, result);
		}
		if (!(ruleFlags & kAuthorityFlagInhibitCache) && !(flags & kSecAssessmentFlagNoCache)) {	// cache inhibit
			if (!info)
				MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
			if (SecTrustRef trust = SecTrustRef(CFDictionaryGetValue(info, kSecCodeInfoTrust))) {
				CFRef<CFDictionaryRef> xinfo;
				MacOSError::check(SecTrustCopyExtendedResult(trust, &xinfo.aref()));
				if (CFDateRef limit = CFDateRef(CFDictionaryGetValue(xinfo, kSecTrustExpirationDate))) {
					this->recordOutcome(code, allow, type, min(expires, dateToJulian(limit)), id);
				}
			}
		}
		if (allow) {
			if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED()) {
				if (!info)
					MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
				CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
				SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path).c_str(), type, label, cdhash ? CFDataGetBytePtr(cdhash) : NULL);
			}
		} else {
			if (SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) {
				if (!info)
					MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
				CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
				std::string cpath = cfString(path);
				const void *hashp = cdhash ? CFDataGetBytePtr(cdhash) : NULL;
				SYSPOLICY_ASSESS_OUTCOME_DENY(cpath.c_str(), type, label, hashp);
				SYSPOLICY_RECORDER_MODE(cpath.c_str(), type, label, hashp, recorder_code_untrusted);
			}
		}
		cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
		addAuthority(result, label, id);
		return;
	}

	if (rc == errSecCSUnsigned) {	// skipped all applicable rules due to pre-screening
		cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
		addAuthority(result, "no usable signature");
		return;
	}
	
	// no applicable authority (but signed, perhaps temporarily). Deny by default
	CFRef<CFDictionaryRef> info;
	MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
	if (flags & kSecAssessmentFlagRequestOrigin) {
		if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates)))
			setOrigin(chain, result);
	}
	if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) {
		CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
		const void *hashp = cdhash ? CFDataGetBytePtr(cdhash) : NULL;
		std::string cpath = cfString(path);
		SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cpath.c_str(), type, latentLabel.c_str(), hashp);
		SYSPOLICY_RECORDER_MODE(cpath.c_str(), type, latentLabel.c_str(), hashp, 0);
	}
	if (!(flags & kSecAssessmentFlagNoCache))
		this->recordOutcome(code, false, type, this->julianNow() + NEGATIVE_HOLD, latentID);
	cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, false);
	addAuthority(result, latentLabel.c_str(), latentID);
}