/*
 * @(#)PolicyParser.java	1.36 01/12/03
 *
 * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.sun.security.auth;

import java.io.*;
import java.lang.RuntimePermission;
import java.net.MalformedURLException;
import java.net.SocketPermission;
import java.net.URL;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Vector;
import java.util.StringTokenizer;
import java.security.GeneralSecurityException;
import sun.security.util.PropertyExpander;

/**
 * The policy for a Java runtime (specifying 
 * which permissions are available for code from various principals)
 * is represented as a separate
 * persistent configuration.  The configuration may be stored as a
 * flat ASCII file, as a serialized binary file of
 * the Policy class, or as a database. <p>
 * 
 * <p>The Java runtime creates one global Policy object, which is used to
 * represent the static policy configuration file.  It is consulted by
 * a ProtectionDomain when the protection domain initializes its set of
 * permissions. <p>
 * 
 * <p>The Policy <code>init</code> method parses the policy 
 * configuration file, and then
 * populates the Policy object.  The Policy object is agnostic in that
 * it is not involved in making policy decisions.  It is merely the
 * Java runtime representation of the persistent policy configuration
 * file. <p>
 * 
 * <p>When a protection domain needs to initialize its set of
 * permissions, it executes code such as the following
 * to ask the global Policy object to populate a
 * Permissions object with the appropriate permissions:
 * <pre>
 *  policy = Policy.getPolicy();
 *  Permissions perms = policy.getPermissions(MyCodeSource)
 * </pre>
 * 
 * <p>The protection domain passes in a CodeSource
 * object, which encapsulates its codebase (URL) and public key attributes.
 * The Policy object evaluates the global policy in light of who the
 * principal is and returns an appropriate Permissions object. 
 *
 * deprecated As of JDK&nbsp;1.4, replaced by
 *             {@link sun.security.provider.PolicyParser}.
 *             This class is entirely deprecated.
 * 
 * @version 1.36, 12/03/01
 * @author Roland Schemers
 *
 * @since JDK1.2
 */

class PolicyParser {

    private static final java.util.ResourceBundle rb =
          (java.util.ResourceBundle)java.security.AccessController.doPrivileged
          (new java.security.PrivilegedAction() {
              public Object run() {
                  return (java.util.ResourceBundle.getBundle
                                ("sun.security.util.AuthResources"));
              }
	  });

    private Vector grantEntries;

    // Convenience variables for parsing
    private static final sun.security.util.Debug debug =
	sun.security.util.Debug.getInstance("parser", "\t[Auth Policy Parser]");
    private StreamTokenizer st;
    private int lookahead;
    private int linenum;
    private boolean expandProp = false;
    private String keyStoreUrlString = null; // unexpanded
    private String keyStoreType = null;

    private String expand(String value)
	throws PropertyExpander.ExpandException
    {
	if (expandProp)
	    return PropertyExpander.expand(value);
	else 
	    return value;
    }
    /** 
     * Creates a PolicyParser object.
     */

    public PolicyParser() {
	grantEntries = new Vector();
    }


    public PolicyParser(boolean expandProp) {
	this();
	this.expandProp = expandProp;
    }

    /** 
     * Reads a policy configuration into the Policy object using a
     * Reader object. <p>
     *
     * @param policy the policy Reader object.
     *
     * @exception ParsingException if the policy configuration contains
     *		a syntax error.
     *
     * @exception IOException if an error occurs while reading the policy
     *		configuration.
     */

    public void read(Reader policy) 
	throws ParsingException, IOException
    {
	if (!(policy instanceof BufferedReader)) {
	    policy = new BufferedReader(policy);
	}

	/**
	 * Configure the stream tokenizer:
	 * 	Recognize strings between "..."
	 * 	Don't convert words to lowercase
	 * 	Recognize both C-style and C++-style comments
	 * 	Treat end-of-line as white space, not as a token
	 */
	st   = new StreamTokenizer(policy);

	st.resetSyntax();
	st.wordChars('a', 'z');
	st.wordChars('A', 'Z');
	st.wordChars('.', '.');
	st.wordChars('0', '9');
	st.wordChars('_', '_');
	st.wordChars('$', '$');
	st.wordChars(128 + 32, 255);
	st.whitespaceChars(0, ' ');
	st.commentChar('/');
	st.quoteChar('\'');
	st.quoteChar('"');
	st.lowerCaseMode(false);
	st.ordinaryChar('/');
	st.slashSlashComments(true);
	st.slashStarComments(true);

	/**
	 * The main parsing loop.  The loop is executed once
	 * for each entry in the config file.      The entries
	 * are delimited by semicolons.   Once we've read in
	 * the information for an entry, go ahead and try to
	 * add it to the policy vector. 
	 * 
	 */ 

	lookahead = st.nextToken();
	while (lookahead != StreamTokenizer.TT_EOF) {
	    if (peek("grant")) {
		GrantEntry ge = parseGrantEntry();
		// could be null if we couldn't expand a property
		if (ge != null)
		    add(ge);
	    } else if (peek("keystore") && keyStoreUrlString==null) {
		// only one keystore entry per policy file, others will be
		// ignored
		parseKeyStoreEntry();
	    } else {
		// error?
	    }
	    match(";");
	}
    }

    public void add(GrantEntry ge)
    {
	grantEntries.addElement(ge);
    }

    public void replace(GrantEntry origGe, GrantEntry newGe)
    {
	grantEntries.setElementAt(newGe, grantEntries.indexOf(origGe));
    }

    public boolean remove(GrantEntry ge)
    {
	return grantEntries.removeElement(ge);
    }

    /**
     * Returns the (possibly expanded) keystore location, or null if the
     * expansion fails.
     */
    public String getKeyStoreUrl() {
	try {
	    if (keyStoreUrlString!=null && keyStoreUrlString.length()!=0) {
		return expand(keyStoreUrlString).replace(File.separatorChar,
							 '/');
	    }
	} catch (PropertyExpander.ExpandException peee) {
	    return null;
	}
	return null;
    }

    public void setKeyStoreUrl(String url) {
	keyStoreUrlString = url;
    }

    public String getKeyStoreType() {
	return keyStoreType;
    }

    public void setKeyStoreType(String type) {
	keyStoreType = type;
    }

    /**
     * Enumerate all the entries in the global policy object. 
     * This method is used by policy admin tools.   The tools
     * should use the Enumeration methods on the returned object
     * to fetch the elements sequentially. 
     */
    public Enumeration grantElements(){
	return grantEntries.elements();
    }

    /** 
     * write out the policy
     */

    public void write(Writer policy) 
    {
	PrintWriter out = new PrintWriter(new BufferedWriter(policy));

	Enumeration enum = grantElements();

	out.println("/* AUTOMATICALLY GENERATED ON "+
		    (new java.util.Date()) + "*/");
	out.println("/* DO NOT EDIT */");
	out.println();

	// write the (unexpanded) keystore entry as the first entry of the
	// policy file
	if (keyStoreUrlString != null) {
	    writeKeyStoreEntry(out);
	}

	// write "grant" entries
	while (enum.hasMoreElements()) {
	    GrantEntry ge = (GrantEntry) enum.nextElement();
	    ge.write(out);
	    out.println();
	}
	out.flush();
    }

    /**
     * parses a keystore entry
     */
    private void parseKeyStoreEntry() throws ParsingException, IOException {
	match("keystore");
	keyStoreUrlString = match("quoted string");

	// parse keystore type
	if (!peek(",")) {
	    return; // default type
	}
	match(",");

	if (peek("\"")) {
	    keyStoreType = match("quoted string");
	} else {
	    throw new ParsingException(st.lineno(),
			rb.getString("expected keystore type"));
	}
    }

    /**
     * writes the (unexpanded) keystore entry
     */
    private void writeKeyStoreEntry(PrintWriter out) {
	out.print("keystore \"");
	out.print(keyStoreUrlString);
	out.print('"');
	if (keyStoreType != null && keyStoreType.length() > 0)
	    out.print(", \"" + keyStoreType + "\"");
	out.println(";");
	out.println();
    }

    /**
     * parse a Grant entry
     */
    private GrantEntry parseGrantEntry()
	throws ParsingException, IOException
    {
	GrantEntry e = new GrantEntry();
	LinkedList principals = null;
	boolean ignoreEntry = false;

	match("grant");

	while(!peek("{")) {

	    if (peekAndMatch("Codebase")) {
		e.codeBase = match("quoted string");
		peekAndMatch(",");
	    } else if (peekAndMatch("SignedBy")) {
		e.signedBy = match("quoted string");
		peekAndMatch(",");
	    } else if (peekAndMatch("Principal")) {
		if (principals == null) {
		    principals = new LinkedList();
		}

		// check for principalClass wildcard
		String principalClass;
		if (peek("*")) {
		    match("*");
		    principalClass = PrincipalEntry.WILDCARD_CLASS;
		} else {
		    principalClass = match("principal type");
		}

		// check for principalName wildcard
		String principalName;
		if (peek("*")) {
		    match("*");
		    principalName = PrincipalEntry.WILDCARD_NAME;
		} else {
		    principalName = match("quoted string");
		}

		// disallow WILDCARD_CLASS && actual name
		if (principalClass.equals(PrincipalEntry.WILDCARD_CLASS) &&
		    !principalName.equals(PrincipalEntry.WILDCARD_NAME)) {
		    if (debug != null)
			debug.println("disallowing principal that has " +
				"WILDCARD class but no WILDCARD name");
		    throw new ParsingException
			(st.lineno(),
			rb.getString("can not specify Principal with a ") +
			rb.getString("wildcard class without a wildcard name"));
		}

		try {
		    principalName = expand(principalName);
		    principals.add
			(new PrincipalEntry(principalClass, principalName));
		} catch (PropertyExpander.ExpandException peee) {
		    // ignore the entire policy entry 
		    // but continue parsing all the info
		    // so we can get to the next entry
		    if (debug != null)
			debug.println("principal name expansion failed: " +
					principalName);
		    ignoreEntry = true;
		}
		peekAndMatch(",");
	    } else {
		throw new 
		 ParsingException(st.lineno(),
			rb.getString("expected codeBase or SignedBy"));
	    }
	}

	// disallow non principal-based grant entries
	if (principals == null) {
	    throw new ParsingException
		(st.lineno(),
		rb.getString("only Principal-based grant entries permitted"));
	}

	e.principals = principals;
	match("{");

	while(!peek("}")) {
 	    if (peek("Permission")) {
		try {
		    PermissionEntry pe = parsePermissionEntry();
		    e.add(pe);
		} catch (PropertyExpander.ExpandException peee) {
		    // ignore. The add never happened
		    skipEntry();  // BugId 4219343
		}
		match(";");
  	    } else {
		throw new 
		    ParsingException(st.lineno(),
		    rb.getString("expected permission entry"));
	    }
	}
	match("}");

	try {
	    if (e.codeBase != null)
	      e.codeBase = expand(e.codeBase).replace(File.separatorChar, '/');
	    e.signedBy = expand(e.signedBy);
	} catch (PropertyExpander.ExpandException peee) {
	    return null;
	}

	return (ignoreEntry == true) ? null : e;
    }

    /**
     * parse a Permission entry
     */
    private PermissionEntry parsePermissionEntry()
	throws ParsingException, IOException, PropertyExpander.ExpandException
    {
	PermissionEntry e = new PermissionEntry();

	// Permission
	match("Permission"); 
	e.permission = match("permission type");

	if (peek("\"")) {
	    // Permission name
	    e.name = expand(match("quoted string")); 
	}

	if (!peek(",")) {
	    return e;
	}
	match(",");

	if (peek("\"")) {
		e.action = expand(match("quoted string"));
		if (!peek(",")) {
		    return e;
		}
		match(",");
	}

	if (peekAndMatch("SignedBy")) {
	    e.signedBy = expand(match("quoted string"));
	}
	return e;
    }

    private boolean peekAndMatch(String expect)
	throws ParsingException, IOException
    {
	if (peek(expect)) {
	    match(expect);
	    return true;
	} else {
	    return false;
	}
    }

    private boolean peek(String expect) {
	boolean found = false;

	switch (lookahead) {

	case StreamTokenizer.TT_WORD:
	    if (expect.equalsIgnoreCase(st.sval)) 
		found = true;
	    break;
	case ',':
	    if (expect.equalsIgnoreCase(","))
		found = true;
	    break;
	case '{':
	    if (expect.equalsIgnoreCase("{"))
		found = true;
	    break;
	case '}':
	    if (expect.equalsIgnoreCase("}"))
		found = true;
	    break;
	case '"':
	    if (expect.equalsIgnoreCase("\""))
		found = true;
	    break;
	case '*':
	    if (expect.equalsIgnoreCase("*"))
		found = true;
	    break;
	default:
	    
	}	
	return found; 
    }

    private String match(String expect) 
	throws ParsingException, IOException
    {
	String value = null;

	switch (lookahead) {
	case StreamTokenizer.TT_NUMBER:
	    throw new ParsingException(st.lineno(), expect, 
					rb.getString("number ") +
					String.valueOf(st.nval));
	case StreamTokenizer.TT_EOF:
	   throw new ParsingException
		(rb.getString("expected ") + expect +
		rb.getString(", read end of file"));
	case StreamTokenizer.TT_WORD:
	    if (expect.equalsIgnoreCase(st.sval)) {
		lookahead = st.nextToken();
	    } else if (expect.equalsIgnoreCase("permission type")) {
		value = st.sval;
		lookahead = st.nextToken();
	    } else if (expect.equalsIgnoreCase("principal type")) {
		value = st.sval;
		lookahead = st.nextToken();
	    } else {
		throw new ParsingException(st.lineno(), expect, st.sval);
	    }
	    break;
	case '"':
	    if (expect.equalsIgnoreCase("quoted string")) {
		value = st.sval;
		lookahead = st.nextToken();
	    } else if (expect.equalsIgnoreCase("permission type")) {
		value = st.sval;
		lookahead = st.nextToken();
	    } else if (expect.equalsIgnoreCase("principal type")) {
		value = st.sval;
		lookahead = st.nextToken();
	    } else {
		throw new ParsingException(st.lineno(), expect, st.sval);
	    }
	    break;
	case ',':
	    if (expect.equalsIgnoreCase(","))
		lookahead = st.nextToken();
	    else
		throw new ParsingException(st.lineno(), expect, ",");
	    break;
	case '{':
	    if (expect.equalsIgnoreCase("{"))
		lookahead = st.nextToken();
	    else
		throw new ParsingException(st.lineno(), expect, "{");
	    break;
	case '}':
	    if (expect.equalsIgnoreCase("}"))
		lookahead = st.nextToken();
	    else
		throw new ParsingException(st.lineno(), expect, "}");
	    break;
	case ';':
	    if (expect.equalsIgnoreCase(";"))
		lookahead = st.nextToken();
	    else
		throw new ParsingException(st.lineno(), expect, ";");
	    break;
	case '*':
	    if (expect.equalsIgnoreCase("*"))
		lookahead = st.nextToken();
	    else
		throw new ParsingException(st.lineno(), expect, "*");
	    break;
	default:
	    throw new ParsingException(st.lineno(), expect, 
			       new String(new char[] {(char)lookahead}));
	}
	return value;
    }

    /**
     * skip all tokens for this entry leaving the delimiter ";"
     * in the stream.
     */
    private void skipEntry()
	throws ParsingException, IOException
    {
      while(lookahead != ';') {
	switch (lookahead) {
	case StreamTokenizer.TT_NUMBER:
	    throw new ParsingException(st.lineno(), ";",
				       rb.getString("number ") +
					String.valueOf(st.nval));
	case StreamTokenizer.TT_EOF:
	  throw new ParsingException
		(rb.getString("expected ';', read end of file"));
	default:
	  lookahead = st.nextToken();
	}
      }
    }

    /**
     * Each grant entry in the policy configuration file is 
     * represented by a
     * GrantEntry object.  <p>
     *
     * <p>
     * For example, the entry
     * <pre>
     *      grant signedBy "Duke" {
     * 		permission java.io.FilePermission "/tmp", "read,write";
     *      };
     *
     * </pre>
     * is represented internally 
     * <pre>
     * 
     * pe = new PermissionEntry("java.io.FilePermission", 
     *                           "/tmp", "read,write");
     *
     * ge = new GrantEntry("Duke", null);
     *
     * ge.add(pe);
     *
     * </pre>
     * 
     * @author Roland Schemers
     *
     * version 1.19, 05/21/98
     */

    static class GrantEntry {

	public String signedBy;
	public String codeBase;
	public LinkedList principals;
	public Vector permissionEntries;

	public GrantEntry() {
	    permissionEntries = new Vector();
	}

	public GrantEntry(String signedBy, String codeBase) {
	    this.codeBase = codeBase;
	    this.signedBy = signedBy;
	    permissionEntries = new Vector();
	}

	public void add(PermissionEntry pe)
	{
	    permissionEntries.addElement(pe);
	}

	public boolean remove(PermissionEntry pe)
	{
	    return permissionEntries.removeElement(pe);
	}

	public boolean contains(PermissionEntry pe)
	{
	    return permissionEntries.contains(pe);
	}

	/**
	 * Enumerate all the permission entries in this GrantEntry.
	 */
	public Enumeration permissionElements(){
	    return permissionEntries.elements();
	}


	public void write(PrintWriter out) {
	    out.print("grant");
	    if (signedBy != null) {
		out.print(" signedBy \"");
		out.print(signedBy);
		out.print('"');
		if (codeBase != null) 
		    out.print(", ");
	    }
	    if (codeBase != null) {
		out.print(" codeBase \"");
		out.print(codeBase);
		out.print('"');
		if (principals != null && principals.size() > 0)
		    out.print(",\n");
	    }
	    if (principals != null && principals.size() > 0) {
		ListIterator pli = principals.listIterator();
		while (pli.hasNext()) {
		    out.print("\tPrincipal ");
		    PrincipalEntry pe = (PrincipalEntry)pli.next();
		    out.print((String)pe.principalClass +
				" \"" + pe.principalName + "\"");
		    if (pli.hasNext())
			out.print(",\n");
		}
	    }
	    out.println(" {");
	    Enumeration enum = permissionEntries.elements();
	    while (enum.hasMoreElements()) {
		PermissionEntry pe =
		    (PermissionEntry) enum.nextElement();
		out.write("  ");
		pe.write(out);
	    }
	    out.println("};");
	}

    }

    /**
     * Principal info (class and name) in a grant entry
     */
    static class PrincipalEntry {

	static final String WILDCARD_CLASS = "WILDCARD_PRINCIPAL_CLASS";
	static final String WILDCARD_NAME = "WILDCARD_PRINCIPAL_NAME";

	String principalClass;
	String principalName;

 	/**
	 * A PrincipalEntry consists of the <code>Principal</code>
	 * class and <code>Principal</code> name.
	 *
	 * <p>
	 *
	 * @param principalClass the <code>Principal</code> class. <p>
	 *
	 * @param principalName the <code>Principal</code> name. <p>
	 */
	public PrincipalEntry(String principalClass, String principalName) {
	    if (principalClass == null || principalName == null)
		throw new NullPointerException
			("null principalClass or principalName");
	    this.principalClass = principalClass;
	    this.principalName = principalName;
	}

	/**
	 * Test for equality between the specified object and this object.
	 * Two PrincipalEntries are equal if their PrincipalClass and
	 * PrincipalName values are equal.
	 *
	 * <p>
	 *
	 * @param obj the object to test for equality with this object.
	 *
	 * @return true if the objects are equal, false otherwise.
	 */
	public boolean equals(Object obj) {
	    if (this == obj)
		return true;

	    if (!(obj instanceof PrincipalEntry))
		return false;

	    PrincipalEntry that = (PrincipalEntry)obj;
	    if (this.principalClass.equals(that.principalClass) &&
		this.principalName.equals(that.principalName)) {
		return true;
	    }

	    return false;
	}

	/**
	 * Return a hashcode for this <code>PrincipalEntry</code>.
	 *
	 * <p>
	 *
	 * @return a hashcode for this <code>PrincipalEntry</code>.
	 */
	public int hashCode() {
	    return principalClass.hashCode();
	}
    }

    /**
     * Each permission entry in the policy configuration file is 
     * represented by a
     * PermissionEntry object.  <p>
     *
     * <p>
     * For example, the entry
     * <pre>
     * 		permission java.io.FilePermission "/tmp", "read,write";
     * </pre>
     * is represented internally 
     * <pre>
     * 
     * pe = new PermissionEntry("java.io.FilePermission", 
     *                           "/tmp", "read,write");
     * </pre>
     * 
     * @author Roland Schemers
     *
     * version 1.19, 05/21/98
     */

    static class PermissionEntry {

	public String permission;
	public String name;
	public String action;
	public String signedBy;

	public PermissionEntry() {
	}

	public PermissionEntry(String permission, 
			String name,
			String action) {
	    this.permission = permission;
	    this.name = name;
	    this.action = action;
	}

	/**
	 * Calculates a hash code value for the object.  Objects
	 * which are equal will also have the same hashcode.
	 */
	public int hashCode() {
	    int retval = permission.hashCode();
	    if (name != null) retval ^= name.hashCode();
	    if (action != null) retval ^= action.hashCode();
	    return retval;
	}

	public boolean equals(Object obj) {
	    if (obj == this)
		return true;

	    if (! (obj instanceof PermissionEntry))
		return false;

	    PermissionEntry that = (PermissionEntry) obj;

	    if (this.permission == null) {
		if (that.permission != null) return false;
	    } else {
		if (!this.permission.equals(that.permission)) return false;
	    }	

	    if (this.name == null) {
		if (that.name != null) return false;
	    } else {
		if (!this.name.equals(that.name)) return false;
	    }	

	    if (this.action == null) {
		if (that.action != null) return false;
	    } else {
		if (!this.action.equals(that.action)) return false;
	    }	

	    if (this.signedBy == null) {
		if (that.signedBy != null) return false;
	    } else {
		if (!this.signedBy.equals(that.signedBy)) return false;
	    }	

	    // everything matched -- the 2 objects are equal
	    return true;
	}

	public void write(PrintWriter out) {
	    out.print("permission ");
	    out.print(permission);
	    if (name != null) {
		out.print(" \"");

		// have to add escape chars for quotes
		if (name.indexOf("\"") != -1) {
		    int numQuotes = 0;
		    char[] chars = name.toCharArray();

		    // count the number of quote chars
		    for (int i = 0; i < chars.length; i++) {
			if (chars[i] == '"')
			    numQuotes++;
		    }

		    // now, add an escape char before each quote
		    char[] newChars = new char[chars.length + numQuotes];
		    for (int i = 0, j = 0; i < chars.length; i++) {
			if (chars[i] != '"') {
			    newChars[j++] = chars[i];
			} else {
			    newChars[j++] = '\\';
			    newChars[j++] = chars[i];
			}
		    }
		    name = new String(newChars);
		}
		out.print(name);
		out.print('"');
	    } 
	    if (action != null) {
		out.print(", \"");
		out.print(action);
		out.print('"');
	    }
	    if (signedBy != null) {
		out.print(", signedBy \"");
		out.print(signedBy);
		out.print('"');
	    }
	    out.println(";");
	}
    }

    static class ParsingException extends GeneralSecurityException {

    /**
     * Constructs a ParsingException with the specified
     * detail message. A detail message is a String that describes
     * this particular exception, which may, for example, specify which
     * algorithm is not available.  
     *
     * @param msg the detail message.  
     */
	public ParsingException(String msg) {
	    super(msg);
	}

	public ParsingException(int line, String msg) {
	    super(rb.getString("line ") + line + rb.getString(": ") + msg);
	}

	public ParsingException(int line, String expect, String actual) {
	    super(rb.getString("line ") + line + rb.getString(": expected '") +
		expect + rb.getString("', found '") + actual +
		rb.getString("'"));
	}
    }

    public static void main(String arg[]) throws Exception {
	PolicyParser pp = new PolicyParser(true);
	pp.read(new FileReader(arg[0]));
	FileWriter fr = new FileWriter(arg[1]);
	pp.write(fr);
        fr.close();
    }
}
