1 /*
   2  * Copyright (c) 1998, 2006, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.security.auth;
  27 
  28 import java.io.*;
  29 import java.lang.RuntimePermission;
  30 import java.net.MalformedURLException;
  31 import java.net.SocketPermission;
  32 import java.net.URL;
  33 import java.util.Enumeration;
  34 import java.util.Hashtable;
  35 import java.util.LinkedList;
  36 import java.util.ListIterator;
  37 import java.util.Vector;
  38 import java.util.StringTokenizer;
  39 import java.security.GeneralSecurityException;
  40 import sun.security.util.PropertyExpander;
  41 
  42 /**
  43  * The policy for a Java runtime (specifying
  44  * which permissions are available for code from various principals)
  45  * is represented as a separate
  46  * persistent configuration.  The configuration may be stored as a
  47  * flat ASCII file, as a serialized binary file of
  48  * the Policy class, or as a database. <p>
  49  *
  50  * <p>The Java runtime creates one global Policy object, which is used to
  51  * represent the static policy configuration file.  It is consulted by
  52  * a ProtectionDomain when the protection domain initializes its set of
  53  * permissions. <p>
  54  *
  55  * <p>The Policy <code>init</code> method parses the policy
  56  * configuration file, and then
  57  * populates the Policy object.  The Policy object is agnostic in that
  58  * it is not involved in making policy decisions.  It is merely the
  59  * Java runtime representation of the persistent policy configuration
  60  * file. <p>
  61  *
  62  * <p>When a protection domain needs to initialize its set of
  63  * permissions, it executes code such as the following
  64  * to ask the global Policy object to populate a
  65  * Permissions object with the appropriate permissions:
  66  * <pre>
  67  *  policy = Policy.getPolicy();
  68  *  Permissions perms = policy.getPermissions(MyCodeSource)
  69  * </pre>
  70  *
  71  * <p>The protection domain passes in a CodeSource
  72  * object, which encapsulates its codebase (URL) and public key attributes.
  73  * The Policy object evaluates the global policy in light of who the
  74  * principal is and returns an appropriate Permissions object.
  75  *
  76  * @deprecated As of JDK&nbsp;1.4, replaced by
  77  *             {@link sun.security.provider.PolicyParser}.
  78  *             This class is entirely deprecated.
  79  *
  80  * @author Roland Schemers
  81  *
  82  * @since 1.2
  83  */
  84 @Deprecated
  85 class PolicyParser {
  86 
  87     private static final java.util.ResourceBundle rb =
  88         java.security.AccessController.doPrivileged
  89         (new java.security.PrivilegedAction<java.util.ResourceBundle>() {
  90             public java.util.ResourceBundle run() {
  91                     return (java.util.ResourceBundle.getBundle
  92                                 ("sun.security.util.AuthResources"));
  93            }
  94         });
  95 
  96     private Vector<GrantEntry> grantEntries;
  97 
  98     // Convenience variables for parsing
  99     private static final sun.security.util.Debug debug =
 100         sun.security.util.Debug.getInstance("parser", "\t[Auth Policy Parser]");
 101     private StreamTokenizer st;
 102     private int lookahead;
 103     private int linenum;
 104     private boolean expandProp = false;
 105     private String keyStoreUrlString = null; // unexpanded
 106     private String keyStoreType = null;
 107 
 108     private String expand(String value)
 109         throws PropertyExpander.ExpandException
 110     {
 111         if (expandProp)
 112             return PropertyExpander.expand(value);
 113         else
 114             return value;
 115     }
 116     /**
 117      * Creates a PolicyParser object.
 118      */
 119 
 120     public PolicyParser() {
 121         grantEntries = new Vector<GrantEntry>();
 122     }
 123 
 124 
 125     public PolicyParser(boolean expandProp) {
 126         this();
 127         this.expandProp = expandProp;
 128     }
 129 
 130     /**
 131      * Reads a policy configuration into the Policy object using a
 132      * Reader object. <p>
 133      *
 134      * @param policy the policy Reader object.
 135      *
 136      * @exception ParsingException if the policy configuration contains
 137      *          a syntax error.
 138      *
 139      * @exception IOException if an error occurs while reading the policy
 140      *          configuration.
 141      */
 142 
 143     public void read(Reader policy)
 144         throws ParsingException, IOException
 145     {
 146         if (!(policy instanceof BufferedReader)) {
 147             policy = new BufferedReader(policy);
 148         }
 149 
 150         /**
 151          * Configure the stream tokenizer:
 152          *      Recognize strings between "..."
 153          *      Don't convert words to lowercase
 154          *      Recognize both C-style and C++-style comments
 155          *      Treat end-of-line as white space, not as a token
 156          */
 157         st   = new StreamTokenizer(policy);
 158 
 159         st.resetSyntax();
 160         st.wordChars('a', 'z');
 161         st.wordChars('A', 'Z');
 162         st.wordChars('.', '.');
 163         st.wordChars('0', '9');
 164         st.wordChars('_', '_');
 165         st.wordChars('$', '$');
 166         st.wordChars(128 + 32, 255);
 167         st.whitespaceChars(0, ' ');
 168         st.commentChar('/');
 169         st.quoteChar('\'');
 170         st.quoteChar('"');
 171         st.lowerCaseMode(false);
 172         st.ordinaryChar('/');
 173         st.slashSlashComments(true);
 174         st.slashStarComments(true);
 175 
 176         /**
 177          * The main parsing loop.  The loop is executed once
 178          * for each entry in the config file.      The entries
 179          * are delimited by semicolons.   Once we've read in
 180          * the information for an entry, go ahead and try to
 181          * add it to the policy vector.
 182          *
 183          */
 184 
 185         lookahead = st.nextToken();
 186         while (lookahead != StreamTokenizer.TT_EOF) {
 187             if (peek("grant")) {
 188                 GrantEntry ge = parseGrantEntry();
 189                 // could be null if we couldn't expand a property
 190                 if (ge != null)
 191                     add(ge);
 192             } else if (peek("keystore") && keyStoreUrlString==null) {
 193                 // only one keystore entry per policy file, others will be
 194                 // ignored
 195                 parseKeyStoreEntry();
 196             } else {
 197                 // error?
 198             }
 199             match(";");
 200         }
 201     }
 202 
 203     public void add(GrantEntry ge)
 204     {
 205         grantEntries.addElement(ge);
 206     }
 207 
 208     public void replace(GrantEntry origGe, GrantEntry newGe)
 209     {
 210         grantEntries.setElementAt(newGe, grantEntries.indexOf(origGe));
 211     }
 212 
 213     public boolean remove(GrantEntry ge)
 214     {
 215         return grantEntries.removeElement(ge);
 216     }
 217 
 218     /**
 219      * Returns the (possibly expanded) keystore location, or null if the
 220      * expansion fails.
 221      */
 222     public String getKeyStoreUrl() {
 223         try {
 224             if (keyStoreUrlString!=null && keyStoreUrlString.length()!=0) {
 225                 return expand(keyStoreUrlString).replace(File.separatorChar,
 226                                                          '/');
 227             }
 228         } catch (PropertyExpander.ExpandException peee) {
 229             return null;
 230         }
 231         return null;
 232     }
 233 
 234     public void setKeyStoreUrl(String url) {
 235         keyStoreUrlString = url;
 236     }
 237 
 238     public String getKeyStoreType() {
 239         return keyStoreType;
 240     }
 241 
 242     public void setKeyStoreType(String type) {
 243         keyStoreType = type;
 244     }
 245 
 246     /**
 247      * Enumerate all the entries in the global policy object.
 248      * This method is used by policy admin tools.   The tools
 249      * should use the Enumeration methods on the returned object
 250      * to fetch the elements sequentially.
 251      */
 252     public Enumeration<GrantEntry> grantElements(){
 253         return grantEntries.elements();
 254     }
 255 
 256     /**
 257      * write out the policy
 258      */
 259 
 260     public void write(Writer policy)
 261     {
 262         PrintWriter out = new PrintWriter(new BufferedWriter(policy));
 263 
 264         Enumeration<GrantEntry> enum_ = grantElements();
 265 
 266         out.println("/* AUTOMATICALLY GENERATED ON "+
 267                     (new java.util.Date()) + "*/");
 268         out.println("/* DO NOT EDIT */");
 269         out.println();
 270 
 271         // write the (unexpanded) keystore entry as the first entry of the
 272         // policy file
 273         if (keyStoreUrlString != null) {
 274             writeKeyStoreEntry(out);
 275         }
 276 
 277         // write "grant" entries
 278         while (enum_.hasMoreElements()) {
 279             GrantEntry ge = enum_.nextElement();
 280             ge.write(out);
 281             out.println();
 282         }
 283         out.flush();
 284     }
 285 
 286     /**
 287      * parses a keystore entry
 288      */
 289     private void parseKeyStoreEntry() throws ParsingException, IOException {
 290         match("keystore");
 291         keyStoreUrlString = match("quoted string");
 292 
 293         // parse keystore type
 294         if (!peek(",")) {
 295             return; // default type
 296         }
 297         match(",");
 298 
 299         if (peek("\"")) {
 300             keyStoreType = match("quoted string");
 301         } else {
 302             throw new ParsingException(st.lineno(),
 303                         rb.getString("expected keystore type"));
 304         }
 305     }
 306 
 307     /**
 308      * writes the (unexpanded) keystore entry
 309      */
 310     private void writeKeyStoreEntry(PrintWriter out) {
 311         out.print("keystore \"");
 312         out.print(keyStoreUrlString);
 313         out.print('"');
 314         if (keyStoreType != null && keyStoreType.length() > 0)
 315             out.print(", \"" + keyStoreType + "\"");
 316         out.println(";");
 317         out.println();
 318     }
 319 
 320     /**
 321      * parse a Grant entry
 322      */
 323     private GrantEntry parseGrantEntry()
 324         throws ParsingException, IOException
 325     {
 326         GrantEntry e = new GrantEntry();
 327         LinkedList<PrincipalEntry> principals = null;
 328         boolean ignoreEntry = false;
 329 
 330         match("grant");
 331 
 332         while(!peek("{")) {
 333 
 334             if (peekAndMatch("Codebase")) {
 335                 e.codeBase = match("quoted string");
 336                 peekAndMatch(",");
 337             } else if (peekAndMatch("SignedBy")) {
 338                 e.signedBy = match("quoted string");
 339                 peekAndMatch(",");
 340             } else if (peekAndMatch("Principal")) {
 341                 if (principals == null) {
 342                     principals = new LinkedList<PrincipalEntry>();
 343                 }
 344 
 345                 // check for principalClass wildcard
 346                 String principalClass;
 347                 if (peek("*")) {
 348                     match("*");
 349                     principalClass = PrincipalEntry.WILDCARD_CLASS;
 350                 } else {
 351                     principalClass = match("principal type");
 352                 }
 353 
 354                 // check for principalName wildcard
 355                 String principalName;
 356                 if (peek("*")) {
 357                     match("*");
 358                     principalName = PrincipalEntry.WILDCARD_NAME;
 359                 } else {
 360                     principalName = match("quoted string");
 361                 }
 362 
 363                 // disallow WILDCARD_CLASS && actual name
 364                 if (principalClass.equals(PrincipalEntry.WILDCARD_CLASS) &&
 365                     !principalName.equals(PrincipalEntry.WILDCARD_NAME)) {
 366                     if (debug != null)
 367                         debug.println("disallowing principal that has " +
 368                                 "WILDCARD class but no WILDCARD name");
 369                     throw new ParsingException
 370                         (st.lineno(),
 371                         rb.getString("can not specify Principal with a ") +
 372                         rb.getString("wildcard class without a wildcard name"));
 373                 }
 374 
 375                 try {
 376                     principalName = expand(principalName);
 377                     principals.add
 378                         (new PrincipalEntry(principalClass, principalName));
 379                 } catch (PropertyExpander.ExpandException peee) {
 380                     // ignore the entire policy entry
 381                     // but continue parsing all the info
 382                     // so we can get to the next entry
 383                     if (debug != null)
 384                         debug.println("principal name expansion failed: " +
 385                                         principalName);
 386                     ignoreEntry = true;
 387                 }
 388                 peekAndMatch(",");
 389             } else {
 390                 throw new
 391                  ParsingException(st.lineno(),
 392                         rb.getString("expected codeBase or SignedBy"));
 393             }
 394         }
 395 
 396         // disallow non principal-based grant entries
 397         if (principals == null) {
 398             throw new ParsingException
 399                 (st.lineno(),
 400                 rb.getString("only Principal-based grant entries permitted"));
 401         }
 402 
 403         e.principals = principals;
 404         match("{");
 405 
 406         while(!peek("}")) {
 407             if (peek("Permission")) {
 408                 try {
 409                     PermissionEntry pe = parsePermissionEntry();
 410                     e.add(pe);
 411                 } catch (PropertyExpander.ExpandException peee) {
 412                     // ignore. The add never happened
 413                     skipEntry();  // BugId 4219343
 414                 }
 415                 match(";");
 416             } else {
 417                 throw new
 418                     ParsingException(st.lineno(),
 419                     rb.getString("expected permission entry"));
 420             }
 421         }
 422         match("}");
 423 
 424         try {
 425             if (e.codeBase != null)
 426               e.codeBase = expand(e.codeBase).replace(File.separatorChar, '/');
 427             e.signedBy = expand(e.signedBy);
 428         } catch (PropertyExpander.ExpandException peee) {
 429             return null;
 430         }
 431 
 432         return (ignoreEntry == true) ? null : e;
 433     }
 434 
 435     /**
 436      * parse a Permission entry
 437      */
 438     private PermissionEntry parsePermissionEntry()
 439         throws ParsingException, IOException, PropertyExpander.ExpandException
 440     {
 441         PermissionEntry e = new PermissionEntry();
 442 
 443         // Permission
 444         match("Permission");
 445         e.permission = match("permission type");
 446 
 447         if (peek("\"")) {
 448             // Permission name
 449             e.name = expand(match("quoted string"));
 450         }
 451 
 452         if (!peek(",")) {
 453             return e;
 454         }
 455         match(",");
 456 
 457         if (peek("\"")) {
 458                 e.action = expand(match("quoted string"));
 459                 if (!peek(",")) {
 460                     return e;
 461                 }
 462                 match(",");
 463         }
 464 
 465         if (peekAndMatch("SignedBy")) {
 466             e.signedBy = expand(match("quoted string"));
 467         }
 468         return e;
 469     }
 470 
 471     private boolean peekAndMatch(String expect)
 472         throws ParsingException, IOException
 473     {
 474         if (peek(expect)) {
 475             match(expect);
 476             return true;
 477         } else {
 478             return false;
 479         }
 480     }
 481 
 482     private boolean peek(String expect) {
 483         boolean found = false;
 484 
 485         switch (lookahead) {
 486 
 487         case StreamTokenizer.TT_WORD:
 488             if (expect.equalsIgnoreCase(st.sval))
 489                 found = true;
 490             break;
 491         case ',':
 492             if (expect.equalsIgnoreCase(","))
 493                 found = true;
 494             break;
 495         case '{':
 496             if (expect.equalsIgnoreCase("{"))
 497                 found = true;
 498             break;
 499         case '}':
 500             if (expect.equalsIgnoreCase("}"))
 501                 found = true;
 502             break;
 503         case '"':
 504             if (expect.equalsIgnoreCase("\""))
 505                 found = true;
 506             break;
 507         case '*':
 508             if (expect.equalsIgnoreCase("*"))
 509                 found = true;
 510             break;
 511         default:
 512 
 513         }
 514         return found;
 515     }
 516 
 517     private String match(String expect)
 518         throws ParsingException, IOException
 519     {
 520         String value = null;
 521 
 522         switch (lookahead) {
 523         case StreamTokenizer.TT_NUMBER:
 524             throw new ParsingException(st.lineno(), expect,
 525                                         rb.getString("number ") +
 526                                         String.valueOf(st.nval));
 527         case StreamTokenizer.TT_EOF:
 528            throw new ParsingException
 529                 (rb.getString("expected ") + expect +
 530                 rb.getString(", read end of file"));
 531         case StreamTokenizer.TT_WORD:
 532             if (expect.equalsIgnoreCase(st.sval)) {
 533                 lookahead = st.nextToken();
 534             } else if (expect.equalsIgnoreCase("permission type")) {
 535                 value = st.sval;
 536                 lookahead = st.nextToken();
 537             } else if (expect.equalsIgnoreCase("principal type")) {
 538                 value = st.sval;
 539                 lookahead = st.nextToken();
 540             } else {
 541                 throw new ParsingException(st.lineno(), expect, st.sval);
 542             }
 543             break;
 544         case '"':
 545             if (expect.equalsIgnoreCase("quoted string")) {
 546                 value = st.sval;
 547                 lookahead = st.nextToken();
 548             } else if (expect.equalsIgnoreCase("permission type")) {
 549                 value = st.sval;
 550                 lookahead = st.nextToken();
 551             } else if (expect.equalsIgnoreCase("principal type")) {
 552                 value = st.sval;
 553                 lookahead = st.nextToken();
 554             } else {
 555                 throw new ParsingException(st.lineno(), expect, st.sval);
 556             }
 557             break;
 558         case ',':
 559             if (expect.equalsIgnoreCase(","))
 560                 lookahead = st.nextToken();
 561             else
 562                 throw new ParsingException(st.lineno(), expect, ",");
 563             break;
 564         case '{':
 565             if (expect.equalsIgnoreCase("{"))
 566                 lookahead = st.nextToken();
 567             else
 568                 throw new ParsingException(st.lineno(), expect, "{");
 569             break;
 570         case '}':
 571             if (expect.equalsIgnoreCase("}"))
 572                 lookahead = st.nextToken();
 573             else
 574                 throw new ParsingException(st.lineno(), expect, "}");
 575             break;
 576         case ';':
 577             if (expect.equalsIgnoreCase(";"))
 578                 lookahead = st.nextToken();
 579             else
 580                 throw new ParsingException(st.lineno(), expect, ";");
 581             break;
 582         case '*':
 583             if (expect.equalsIgnoreCase("*"))
 584                 lookahead = st.nextToken();
 585             else
 586                 throw new ParsingException(st.lineno(), expect, "*");
 587             break;
 588         default:
 589             throw new ParsingException(st.lineno(), expect,
 590                                new String(new char[] {(char)lookahead}));
 591         }
 592         return value;
 593     }
 594 
 595     /**
 596      * skip all tokens for this entry leaving the delimiter ";"
 597      * in the stream.
 598      */
 599     private void skipEntry()
 600         throws ParsingException, IOException
 601     {
 602       while(lookahead != ';') {
 603         switch (lookahead) {
 604         case StreamTokenizer.TT_NUMBER:
 605             throw new ParsingException(st.lineno(), ";",
 606                                        rb.getString("number ") +
 607                                         String.valueOf(st.nval));
 608         case StreamTokenizer.TT_EOF:
 609           throw new ParsingException
 610                 (rb.getString("expected ';', read end of file"));
 611         default:
 612           lookahead = st.nextToken();
 613         }
 614       }
 615     }
 616 
 617     /**
 618      * Each grant entry in the policy configuration file is
 619      * represented by a
 620      * GrantEntry object.  <p>
 621      *
 622      * <p>
 623      * For example, the entry
 624      * <pre>
 625      *      grant signedBy "Duke" {
 626      *          permission java.io.FilePermission "/tmp", "read,write";
 627      *      };
 628      *
 629      * </pre>
 630      * is represented internally
 631      * <pre>
 632      *
 633      * pe = new PermissionEntry("java.io.FilePermission",
 634      *                           "/tmp", "read,write");
 635      *
 636      * ge = new GrantEntry("Duke", null);
 637      *
 638      * ge.add(pe);
 639      *
 640      * </pre>
 641      *
 642      * @author Roland Schemers
 643      *
 644      * version 1.19, 05/21/98
 645      */
 646 
 647     static class GrantEntry {
 648 
 649         public String signedBy;
 650         public String codeBase;
 651         public LinkedList<PrincipalEntry> principals;
 652         public Vector<PermissionEntry> permissionEntries;
 653 
 654         public GrantEntry() {
 655             permissionEntries = new Vector<PermissionEntry>();
 656         }
 657 
 658         public GrantEntry(String signedBy, String codeBase) {
 659             this.codeBase = codeBase;
 660             this.signedBy = signedBy;
 661             permissionEntries = new Vector<PermissionEntry>();
 662         }
 663 
 664         public void add(PermissionEntry pe)
 665         {
 666             permissionEntries.addElement(pe);
 667         }
 668 
 669         public boolean remove(PermissionEntry pe)
 670         {
 671             return permissionEntries.removeElement(pe);
 672         }
 673 
 674         public boolean contains(PermissionEntry pe)
 675         {
 676             return permissionEntries.contains(pe);
 677         }
 678 
 679         /**
 680          * Enumerate all the permission entries in this GrantEntry.
 681          */
 682         public Enumeration<PermissionEntry> permissionElements(){
 683             return permissionEntries.elements();
 684         }
 685 
 686 
 687         public void write(PrintWriter out) {
 688             out.print("grant");
 689             if (signedBy != null) {
 690                 out.print(" signedBy \"");
 691                 out.print(signedBy);
 692                 out.print('"');
 693                 if (codeBase != null)
 694                     out.print(", ");
 695             }
 696             if (codeBase != null) {
 697                 out.print(" codeBase \"");
 698                 out.print(codeBase);
 699                 out.print('"');
 700                 if (principals != null && principals.size() > 0)
 701                     out.print(",\n");
 702             }
 703             if (principals != null && principals.size() > 0) {
 704                 ListIterator<PrincipalEntry> pli = principals.listIterator();
 705                 while (pli.hasNext()) {
 706                     out.print("\tPrincipal ");
 707                     PrincipalEntry pe = pli.next();
 708                     out.print(pe.principalClass +
 709                                 " \"" + pe.principalName + "\"");
 710                     if (pli.hasNext())
 711                         out.print(",\n");
 712                 }
 713             }
 714             out.println(" {");
 715             Enumeration<PermissionEntry> enum_ = permissionEntries.elements();
 716             while (enum_.hasMoreElements()) {
 717                 PermissionEntry pe = enum_.nextElement();
 718                 out.write("  ");
 719                 pe.write(out);
 720             }
 721             out.println("};");
 722         }
 723 
 724     }
 725 
 726     /**
 727      * Principal info (class and name) in a grant entry
 728      */
 729     static class PrincipalEntry {
 730 
 731         static final String WILDCARD_CLASS = "WILDCARD_PRINCIPAL_CLASS";
 732         static final String WILDCARD_NAME = "WILDCARD_PRINCIPAL_NAME";
 733 
 734         String principalClass;
 735         String principalName;
 736 
 737         /**
 738          * A PrincipalEntry consists of the <code>Principal</code>
 739          * class and <code>Principal</code> name.
 740          *
 741          * <p>
 742          *
 743          * @param principalClass the <code>Principal</code> class. <p>
 744          *
 745          * @param principalName the <code>Principal</code> name. <p>
 746          */
 747         public PrincipalEntry(String principalClass, String principalName) {
 748             if (principalClass == null || principalName == null)
 749                 throw new NullPointerException
 750                         ("null principalClass or principalName");
 751             this.principalClass = principalClass;
 752             this.principalName = principalName;
 753         }
 754 
 755         /**
 756          * Test for equality between the specified object and this object.
 757          * Two PrincipalEntries are equal if their PrincipalClass and
 758          * PrincipalName values are equal.
 759          *
 760          * <p>
 761          *
 762          * @param obj the object to test for equality with this object.
 763          *
 764          * @return true if the objects are equal, false otherwise.
 765          */
 766         public boolean equals(Object obj) {
 767             if (this == obj)
 768                 return true;
 769 
 770             if (!(obj instanceof PrincipalEntry))
 771                 return false;
 772 
 773             PrincipalEntry that = (PrincipalEntry)obj;
 774             if (this.principalClass.equals(that.principalClass) &&
 775                 this.principalName.equals(that.principalName)) {
 776                 return true;
 777             }
 778 
 779             return false;
 780         }
 781 
 782         /**
 783          * Return a hashcode for this <code>PrincipalEntry</code>.
 784          *
 785          * <p>
 786          *
 787          * @return a hashcode for this <code>PrincipalEntry</code>.
 788          */
 789         public int hashCode() {
 790             return principalClass.hashCode();
 791         }
 792     }
 793 
 794     /**
 795      * Each permission entry in the policy configuration file is
 796      * represented by a
 797      * PermissionEntry object.  <p>
 798      *
 799      * <p>
 800      * For example, the entry
 801      * <pre>
 802      *          permission java.io.FilePermission "/tmp", "read,write";
 803      * </pre>
 804      * is represented internally
 805      * <pre>
 806      *
 807      * pe = new PermissionEntry("java.io.FilePermission",
 808      *                           "/tmp", "read,write");
 809      * </pre>
 810      *
 811      * @author Roland Schemers
 812      *
 813      * version 1.19, 05/21/98
 814      */
 815 
 816     static class PermissionEntry {
 817 
 818         public String permission;
 819         public String name;
 820         public String action;
 821         public String signedBy;
 822 
 823         public PermissionEntry() {
 824         }
 825 
 826         public PermissionEntry(String permission,
 827                         String name,
 828                         String action) {
 829             this.permission = permission;
 830             this.name = name;
 831             this.action = action;
 832         }
 833 
 834         /**
 835          * Calculates a hash code value for the object.  Objects
 836          * which are equal will also have the same hashcode.
 837          */
 838         public int hashCode() {
 839             int retval = permission.hashCode();
 840             if (name != null) retval ^= name.hashCode();
 841             if (action != null) retval ^= action.hashCode();
 842             return retval;
 843         }
 844 
 845         public boolean equals(Object obj) {
 846             if (obj == this)
 847                 return true;
 848 
 849             if (! (obj instanceof PermissionEntry))
 850                 return false;
 851 
 852             PermissionEntry that = (PermissionEntry) obj;
 853 
 854             if (this.permission == null) {
 855                 if (that.permission != null) return false;
 856             } else {
 857                 if (!this.permission.equals(that.permission)) return false;
 858             }
 859 
 860             if (this.name == null) {
 861                 if (that.name != null) return false;
 862             } else {
 863                 if (!this.name.equals(that.name)) return false;
 864             }
 865 
 866             if (this.action == null) {
 867                 if (that.action != null) return false;
 868             } else {
 869                 if (!this.action.equals(that.action)) return false;
 870             }
 871 
 872             if (this.signedBy == null) {
 873                 if (that.signedBy != null) return false;
 874             } else {
 875                 if (!this.signedBy.equals(that.signedBy)) return false;
 876             }
 877 
 878             // everything matched -- the 2 objects are equal
 879             return true;
 880         }
 881 
 882         public void write(PrintWriter out) {
 883             out.print("permission ");
 884             out.print(permission);
 885             if (name != null) {
 886                 out.print(" \"");
 887 
 888                 // have to add escape chars for quotes
 889                 if (name.indexOf("\"") != -1) {
 890                     int numQuotes = 0;
 891                     char[] chars = name.toCharArray();
 892 
 893                     // count the number of quote chars
 894                     for (int i = 0; i < chars.length; i++) {
 895                         if (chars[i] == '"')
 896                             numQuotes++;
 897                     }
 898 
 899                     // now, add an escape char before each quote
 900                     char[] newChars = new char[chars.length + numQuotes];
 901                     for (int i = 0, j = 0; i < chars.length; i++) {
 902                         if (chars[i] != '"') {
 903                             newChars[j++] = chars[i];
 904                         } else {
 905                             newChars[j++] = '\\';
 906                             newChars[j++] = chars[i];
 907                         }
 908                     }
 909                     name = new String(newChars);
 910                 }
 911                 out.print(name);
 912                 out.print('"');
 913             }
 914             if (action != null) {
 915                 out.print(", \"");
 916                 out.print(action);
 917                 out.print('"');
 918             }
 919             if (signedBy != null) {
 920                 out.print(", signedBy \"");
 921                 out.print(signedBy);
 922                 out.print('"');
 923             }
 924             out.println(";");
 925         }
 926     }
 927 
 928     static class ParsingException extends GeneralSecurityException {
 929 
 930         private static final long serialVersionUID = 8240970523155877400L;
 931 
 932         /**
 933          * Constructs a ParsingException with the specified
 934          * detail message. A detail message is a String that describes
 935          * this particular exception, which may, for example, specify which
 936          * algorithm is not available.
 937          *
 938          * @param msg the detail message.
 939          */
 940         public ParsingException(String msg) {
 941             super(msg);
 942         }
 943 
 944         public ParsingException(int line, String msg) {
 945             super(rb.getString("line ") + line + rb.getString(": ") + msg);
 946         }
 947 
 948         public ParsingException(int line, String expect, String actual) {
 949             super(rb.getString("line ") + line + rb.getString(": expected '") +
 950                 expect + rb.getString("', found '") + actual +
 951                 rb.getString("'"));
 952         }
 953     }
 954 
 955     public static void main(String arg[]) throws Exception {
 956         PolicyParser pp = new PolicyParser(true);
 957         pp.read(new FileReader(arg[0]));
 958         FileWriter fr = new FileWriter(arg[1]);
 959         pp.write(fr);
 960         fr.close();
 961     }
 962 }