/***********************************************
* Columbia University CS Department
* Network Security Lab
*
* Open and Survivable Embedded Systems (OASES)
*
* Structured Policy Command Language (SPCL)
*
*
* This software is provided as is without any
* warranty or guarantee of merchantibility
* or fitness for a particular purpose. This
* software is released under the terms of the
* GNU General Public License (GPL).
*
* Last updated: May 9, 2003
*
***********************************************/

package spcl.data;

import java.util.Vector;

/**
 * The ultra important Rule class.
 *
 * PolicyEngine collects an array of values from
 *  evaluate(). If this array contains oneof either
 *  DENY or ALLOW and the rest WISHY-WASHY, policy is
 *  consistent and returns the DENY or ALLOW. If there is
 *  a mixture, the policy is inconsistent and returns DENY.
 *
 *
 * @author locasto@cs.columbia.edu
*/
public class Rule{

  public static final int ALLOW = 100;
  public static final int WISHY_WASHY = 50;
  public static final int DENY = 200;

  public static final int STAR = 1024;

  private String name;
  private Vector firings = new Vector();
  private Vector conditions = new Vector();
  private boolean active = false;
  private boolean appliesToAll=false;
  private Principal principal = null;
  private Group group = null;
  private SystemObject so=null;
  private Action action = null;
  private int response;

  public Rule( String n ){
    this.name = n;
  }

  public String getName(){
    return this.name;
  }

  public void addFiring( Firing f ){
    if( f == null ){
       return;
    }else{
       firings.addElement( f );
    }
  }

  /**
   * Set the principal for this rule.
   *@deprecated - replaced by the setOwner() method
  */
  public void setPrincipal( Principal p ){
    this.principal = p;
  }

  public void setOwner( Principal p ){
    this.principal = p;
  }

  public void setOwner( Group g ){
    this.group = g;
  }

  public void setObject( SystemObject sysObj ){
    this.so = sysObj;
  }

  public void setAccess( int res ){
    System.err.println( name+" access is "+res );
    switch( res ){
       case Rule.DENY:
           response=Rule.DENY;
           break;
       case Rule.ALLOW:
            response=Rule.ALLOW;
           break;
       default:
          response=Rule.DENY;
    }
    System.err.println( name+" access is "+response );
  }

  public void setAction( int blanket ){
      //for any object, if this rule has matched, return response
      if( blanket == spcl.data.Rule.STAR )
      appliesToAll=true;
	  action = new Action( "APPLY_ALL" );
	  action.setActionPattern( ".*" );
  }

  public void setAction( Action a ){
    this.action = a;
  }

  public void setActive( boolean b ){
    active = b;
    if( active ) System.err.println( "\t>> "+name+" is active." );
    else System.err.println( "\t>> "+name+" is deactivated." );
  }

  public boolean isActive(){
    return active;
  }

  //give this some args
  //params = Vector of valued-Variables
  //@returns DENY.. if rule says deny
  //@returns ALLOW .. if rule says allow
  //@returns NO_DEC .. if rule does not apply to object or
  //   conditions DO NOT UNIFY
  public int evaluate( Vector params, String actionString, SystemObject clientOn ){
	String regexp = action.getActionPattern();
	Vector unifiedConditions=null;
	int psize = params.size();
	int csize = conditions.size();

	if( psize!=csize && csize!=0 ){
		System.err.println( "\t\t\t>> rule.evaluate()::psize("+psize+")!=csize("+csize+")" );
		return Rule.WISHY_WASHY;
	}

	//more specific first
	if( so!=clientOn ){
		System.err.println( "\t\t\t>> rule.evaluate()::so!=current.object " );
		return Rule.WISHY_WASHY;
	}

	if( !subExpression( regexp, actionString ) ){
		System.err.println( "\t\t\t>> actionString is !subexp( regexp )" );
		return Rule.WISHY_WASHY;
	}
	if( (unifiedConditions = unifyConditions( params ))==null ){
		System.err.println( "\t\t\t>> unifiedConditions=null " );
		return Rule.WISHY_WASHY;
	}else{
		//go through unifiedConditions and see if all eval to true
		if( evaluateConditions( unifiedConditions ) ){
			return response;
		}else{
			return Rule.WISHY_WASHY; //should be DENY?
		}
	}
  }

  /** @requires Java 1.4 */
  private boolean subExpression( String regexp, String actionString ){
    boolean match=false;
	try{
		match = actionString.matches( regexp );
	}catch( Exception REparseEx ){
		return (actionString.equals( regexp ));
	}
	return match;
  }

  /** given the unified conditions, we can now evaluate them to a
   * boolean AND of each condition. We had their relop and their rval,
   * and we got their lvalue from unifyConditions()
   *
  */
  private boolean evaluateConditions( Vector uconds ){
    boolean truth=true;
	boolean tempTruth=false;
	Condition tempCond = null;
	for( int i=0;i<uconds.size();i++ ){
		tempCond = (Condition)uconds.elementAt(i);
		tempTruth = tempCond.eval();
		System.err.println( "\t\t\t>>> cond["+tempCond.toString()+"]="+tempTruth );
		truth = (truth && tempTruth);
	}
	return truth;
  }

  /** call from rule.evaluate()
   * find and return the conditions that match
   * need to determine what is in params first!!!
   * basically, it'll just be AND x=6 AND y=true
   *  so in the PolicyEngine, we have to correctly parse this
   *  and create the appropriately typed variables
   * must unify on TYPE and NAME...then <-param sets the value
   *
   * add to results the first condition that unifies with a
   *  given param, and go on to next Condition
  */
  private Vector unifyConditions( Vector params ){
	Vector results=new Vector();
	Condition tempCond=null;
	Variable tempParam=null;
	int tempType=-1;
	
	//tempCond.var.type will not change
	//only tempCond.var.svalue,bvalue,nvalue
	for( int i=0;i<conditions.size();i++ ){
		tempCond = (Condition)conditions.elementAt( i );
		System.err.print( "\t\t\t>> unify("+tempCond.var.getName()+", ");
		for( int j=0;j<params.size();j++ ){
			tempParam = (Variable)params.elementAt(j);
			System.err.print( tempParam.getName()+" );" );
			if( tempParam.getName().equals( tempCond.var.getName() )
			    && (tempParam.getType()==tempCond.var.getType())
			){
				System.err.print( " = success\n" );
				//condition lvalue matched in type and name with a param
				//unify
				tempType = tempParam.getType();
				switch( tempType ){
					case Variable.NUMBER:
						tempCond.var.setValue( ""+tempParam.getNumberValue() );
						break;
					case Variable.STRING:
						tempCond.var.setValue( tempParam.getStringValue() );
						break;
					case Variable.BOOLEAN:
						tempCond.var.setValue( ""+tempParam.getBooleanValue() );
						break;
				}
				results.add( tempCond );
			}else{
				System.err.print( " = fail\n" );
			}
		}
		System.err.println( "" );
	}

	//must have supplied a value for every condition!!
	if( results.size() != conditions.size() ) return null;
	return results;
  }

  public void addCondition( Variable var, String relOp, boolean rval ){
		conditions.add( new Condition( var, relOp, rval ) );
  }

  public void addCondition( Variable var, String relOp, String rval ){
		conditions.add( new Condition( var, relOp, rval ) );
  }

  public synchronized void fire(){
    int allfirings = firings.size();
    Firing f = null;
    for( int i=0;i<allfirings;i++ ){
      f = ((Firing)firings.elementAt(i));
      if( f!=null )
        f.doFire();
    }
  }

}
