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


//
// acl_preauth - a subject type for modeling PINs and similar slot-specific
//		pre-authentication schemes.
//
#include "acl_preauth.h"
#include <security_utilities/debugging.h>


namespace Security {
namespace PreAuthorizationAcls {


//
// Origin forms
//
AclSubject *OriginMaker::make(const TypedList &list) const
{
	ListElement *args[1];
	crack(list, 1, args, CSSM_LIST_ELEMENT_WORDID);
		return new OriginAclSubject(*args[0]);
}

AclSubject *OriginMaker::make(AclSubject::Version version, Reader &pub, Reader &) const
{
	// just an integer containing the auth tag
	Endian<uint32> auth;
	pub(auth);
	return new OriginAclSubject(AclAuthorization(auth));
}


//
// Validate the origin form.
// This tries to find the source AclObject and hands the question off to it.
// If anything isn't right, fail the validation.
//
bool OriginAclSubject::validate(const AclValidationContext &ctx) const
{
	if (Environment *env = ctx.environment<Environment>())
		if (ObjectAcl *source = env->preAuthSource())
			if (source->validates(mAuthTag, ctx.cred(), ctx.environment()))
				return true;

	// no joy (the sad default)
	return false;
}


CssmList OriginAclSubject::toList(Allocator &alloc) const
{
	return TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_PREAUTH,
		new(alloc) ListElement(mAuthTag));
}

OriginAclSubject::OriginAclSubject(AclAuthorization auth)
	: AclSubject(CSSM_ACL_SUBJECT_TYPE_PREAUTH), mAuthTag(auth)
{
	if (auth < CSSM_ACL_AUTHORIZATION_PREAUTH_BASE || auth >= CSSM_ACL_AUTHORIZATION_PREAUTH_END)
		CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
}


//
// Origin exported form is just a four-byte integer (preauth authorization tag)
//
void OriginAclSubject::exportBlob(Writer::Counter &pub, Writer::Counter &priv)
{
	Endian<uint32> auth = mAuthTag;
	pub(auth);
}

void OriginAclSubject::exportBlob(Writer &pub, Writer &priv)
{
	Endian<uint32> auth = mAuthTag;
	pub(auth);
}


//
// Now for the other side of the coin.
// SourceAclSubjects describe the unusual side (for ACL management) of this game.
// The AclSubject of a preauth source MUST be of PREAUTH_SOURCE type. This subject
// contains the actual validation conditions as a sub-subject, and may provide
// additional information to represent known state of the preauth system.
//
// Think of the extra data in a PreAuthSource ACL as "current state informational"
// that only exists internally, and in the CssmList view. It does not get put into
// persistent (externalized) ACL storage at all. (After all, there's nothing persistent
// about it.)
//
AclSubject *SourceMaker::make(const TypedList &list) const
{
	// minimum requirement: item[1] = sub-subject (sublist)
	if (list.length() < 2)
        CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
	ListElement &sub = list[1];
	RefPointer<AclSubject> subSubject = ObjectAcl::make(sub);
	
	// anything else is interpreted as tracking state (defaulted if missing)
	switch (list.length()) {
	case 2:		// no tracking state
		return new SourceAclSubject(subSubject);
	case 3:
		if (list[2].type() == CSSM_LIST_ELEMENT_WORDID)
			return new SourceAclSubject(subSubject, list[2]);
		// fall through
	default:
		CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
	}
}

AclSubject *SourceMaker::make(AclSubject::Version version, Reader &pub, Reader &priv) const
{
	// external form does not contain tracking state - defaults to unknown
	RefPointer<AclSubject> subSubject = ObjectAcl::importSubject(pub, priv);
	return new SourceAclSubject(subSubject);
}


//
// Source validation uses its own home-cooked validation context.
//
class SourceValidationContext : public AclValidationContext {
public:
	SourceValidationContext(const AclValidationContext &base)
		: AclValidationContext(base), mCredTag(base.entryTag()) { }
		
	uint32 count() const	{ return cred() ? cred()->samples().length() : 0; }
	uint32 size() const		{ return count(); }
	const TypedList &sample(uint32 n) const
	{ assert(n < count()); return cred()->samples()[n]; }
	
	const char *credTag() const { return mCredTag; }	// override
	
	void matched(const TypedList *) const { }	//@@@ prelim
	
private:
	const char *mCredTag;
};

bool SourceAclSubject::SourceAclSubject::validate(const AclValidationContext &baseCtx) const
{
	// try to authenticate our sub-subject
	if (Environment *env = baseCtx.environment<Environment>()) {
		bool &accepted = env->store(this).attachment<Store>(this).accepted;
		if (!accepted) {
			SourceValidationContext ctx(baseCtx);
			if (mSourceSubject->validate(ctx)) {
				secdebug("preauth", "%p pre-authenticated", this);
				accepted = true;
			}
		}
		return accepted;
	}
	return false;
}


CssmList SourceAclSubject::toList(Allocator &alloc) const
{
	return TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_PREAUTH_SOURCE,
		new(alloc) ListElement(mSourceSubject->toList(alloc)),
		new(alloc) ListElement(mTrackingState));
}


SourceAclSubject::SourceAclSubject(AclSubject *subSubject, CSSM_ACL_PREAUTH_TRACKING_STATE state)
	: AclSubject(CSSM_ACL_SUBJECT_TYPE_PREAUTH),
	  mSourceSubject(subSubject),
	  mTrackingState(state)
{
}


//
// Export the subject to a memory blob
//
void SourceAclSubject::exportBlob(Writer::Counter &pub, Writer::Counter &priv)
{
	mSourceSubject->exportBlob(pub, priv);
}

void SourceAclSubject::exportBlob(Writer &pub, Writer &priv)
{
	mSourceSubject->exportBlob(pub, priv);
	// tracking state is not exported
}


#ifdef DEBUGDUMP

void OriginAclSubject::debugDump() const
{
	Debug::dump("Preauth(to slot %ld)", mAuthTag - CSSM_ACL_AUTHORIZATION_PREAUTH_BASE);
}

void SourceAclSubject::debugDump() const
{
	Debug::dump("Preauth source: ");
	if (mSourceSubject)
		mSourceSubject->debugDump();
	else
		Debug::dump("NULL?");
	if (mTrackingState & CSSM_ACL_PREAUTH_TRACKING_UNKNOWN) {
		Debug::dump(" track=unknown");
	} else {
		Debug::dump(" retries=%d", trackingRetries());
		if (trackingAuthorized())
			Debug::dump(" authorized");
	}
}

#endif //DEBUGDUMP


}	//  namespace PreAuthorizationAcls
}	//  namespace Security
