1 /*
   2  * Copyright (c) 1997, 2018, 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 sun.security.provider;
  27 
  28 import java.io.*;
  29 import java.security.GeneralSecurityException;
  30 import java.security.Principal;
  31 import java.util.*;
  32 import javax.security.auth.x500.X500Principal;
  33 
  34 import sun.security.util.Debug;
  35 import sun.security.util.PropertyExpander;
  36 import sun.security.util.LocalizedMessage;
  37 
  38 /**
  39  * The policy for a Java runtime (specifying
  40  * which permissions are available for code from various principals)
  41  * is represented as a separate
  42  * persistent configuration.  The configuration may be stored as a
  43  * flat ASCII file, as a serialized binary file of
  44  * the Policy class, or as a database.
  45  *
  46  * <p>The Java runtime creates one global Policy object, which is used to
  47  * represent the static policy configuration file.  It is consulted by
  48  * a ProtectionDomain when the protection domain initializes its set of
  49  * permissions.
  50  *
  51  * <p>The Policy <code>init</code> method parses the policy
  52  * configuration file, and then
  53  * populates the Policy object.  The Policy object is agnostic in that
  54  * it is not involved in making policy decisions.  It is merely the
  55  * Java runtime representation of the persistent policy configuration
  56  * file.
  57  *
  58  * <p>When a protection domain needs to initialize its set of
  59  * permissions, it executes code such as the following
  60  * to ask the global Policy object to populate a
  61  * Permissions object with the appropriate permissions:
  62  * <pre>
  63  *  policy = Policy.getPolicy();
  64  *  Permissions perms = policy.getPermissions(protectiondomain)
  65  * </pre>
  66  *
  67  * <p>The protection domain contains a CodeSource
  68  * object, which encapsulates its codebase (URL) and public key attributes.
  69  * It also contains the principals associated with the domain.
  70  * The Policy object evaluates the global policy in light of who the
  71  * principal is and what the code source is and returns an appropriate
  72  * Permissions object.
  73  *
  74  * @author Roland Schemers
  75  * @author Ram Marti
  76  *
  77  * @since 1.2
  78  */
  79 
  80 public class PolicyParser {
  81 
  82     private Vector<GrantEntry> grantEntries;
  83     private Map<String, DomainEntry> domainEntries;
  84 
  85     // Convenience variables for parsing
  86     private static final Debug debug = Debug.getInstance("parser",
  87                                                 "\t[Policy Parser]");
  88     private StreamTokenizer st;
  89     private int lookahead;
  90     private boolean expandProp = false;
  91     private String keyStoreUrlString = null; // unexpanded
  92     private String keyStoreType = null;
  93     private String keyStoreProvider = null;
  94     private String storePassURL = null;
  95 
  96     private String expand(String value)
  97         throws PropertyExpander.ExpandException
  98     {
  99         return expand(value, false);
 100     }
 101 
 102     private String expand(String value, boolean encodeURL)
 103         throws PropertyExpander.ExpandException
 104     {
 105         if (!expandProp) {
 106             return value;
 107         } else {
 108             return PropertyExpander.expand(value, encodeURL);
 109         }
 110     }
 111 
 112     /**
 113      * Creates a PolicyParser object.
 114      */
 115 
 116     public PolicyParser() {
 117         grantEntries = new Vector<GrantEntry>();
 118     }
 119 
 120 
 121     public PolicyParser(boolean expandProp) {
 122         this();
 123         this.expandProp = expandProp;
 124     }
 125 
 126     /**
 127      * Reads a policy configuration into the Policy object using a
 128      * Reader object. <p>
 129      *
 130      * @param policy the policy Reader object.
 131      *
 132      * @exception ParsingException if the policy configuration contains
 133      *          a syntax error.
 134      *
 135      * @exception IOException if an error occurs while reading the policy
 136      *          configuration.
 137      */
 138 
 139     public void read(Reader policy)
 140         throws ParsingException, IOException
 141     {
 142         if (!(policy instanceof BufferedReader)) {
 143             policy = new BufferedReader(policy);
 144         }
 145 
 146         /**
 147          * Configure the stream tokenizer:
 148          *      Recognize strings between "..."
 149          *      Don't convert words to lowercase
 150          *      Recognize both C-style and C++-style comments
 151          *      Treat end-of-line as white space, not as a token
 152          */
 153         st   = new StreamTokenizer(policy);
 154 
 155         st.resetSyntax();
 156         st.wordChars('a', 'z');
 157         st.wordChars('A', 'Z');
 158         st.wordChars('.', '.');
 159         st.wordChars('0', '9');
 160         st.wordChars('_', '_');
 161         st.wordChars('$', '$');
 162         st.wordChars(128 + 32, 255);
 163         st.whitespaceChars(0, ' ');
 164         st.commentChar('/');
 165         st.quoteChar('\'');
 166         st.quoteChar('"');
 167         st.lowerCaseMode(false);
 168         st.ordinaryChar('/');
 169         st.slashSlashComments(true);
 170         st.slashStarComments(true);
 171 
 172         /**
 173          * The main parsing loop.  The loop is executed once
 174          * for each entry in the config file.      The entries
 175          * are delimited by semicolons.   Once we've read in
 176          * the information for an entry, go ahead and try to
 177          * add it to the policy vector.
 178          *
 179          */
 180 
 181         lookahead = st.nextToken();
 182         GrantEntry ge = null;
 183         while (lookahead != StreamTokenizer.TT_EOF) {
 184             if (peek("grant")) {
 185                 ge = parseGrantEntry();
 186                 // could be null if we couldn't expand a property
 187                 if (ge != null)
 188                     add(ge);
 189             } else if (peek("keystore") && keyStoreUrlString==null) {
 190                 // only one keystore entry per policy file, others will be
 191                 // ignored
 192                 parseKeyStoreEntry();
 193             } else if (peek("keystorePasswordURL") && storePassURL==null) {
 194                 // only one keystore passwordURL per policy file, others will be
 195                 // ignored
 196                 parseStorePassURL();
 197             } else if (ge == null && keyStoreUrlString == null &&
 198                 storePassURL == null && peek("domain")) {
 199                 if (domainEntries == null) {
 200                     domainEntries = new TreeMap<>();
 201                 }
 202                 DomainEntry de = parseDomainEntry();
 203                 if (de != null) {
 204                     String domainName = de.getName();
 205                     if (!domainEntries.containsKey(domainName)) {
 206                         domainEntries.put(domainName, de);
 207                     } else {
 208                         LocalizedMessage localizedMsg = new LocalizedMessage(
 209                             "duplicate.keystore.domain.name");
 210                         Object[] source = {domainName};
 211                         String msg = "duplicate keystore domain name: " +
 212                                      domainName;
 213                         throw new ParsingException(msg, localizedMsg, source);
 214                     }
 215                 }
 216             } else {
 217                 // error?
 218             }
 219             match(";");
 220         }
 221 
 222         if (keyStoreUrlString == null && storePassURL != null) {
 223             throw new ParsingException(LocalizedMessage.getNonlocalized
 224                 ("keystorePasswordURL.can.not.be.specified.without.also.specifying.keystore"));
 225         }
 226     }
 227 
 228     public void add(GrantEntry ge)
 229     {
 230         grantEntries.addElement(ge);
 231     }
 232 
 233     public void replace(GrantEntry origGe, GrantEntry newGe)
 234     {
 235         grantEntries.setElementAt(newGe, grantEntries.indexOf(origGe));
 236     }
 237 
 238     public boolean remove(GrantEntry ge)
 239     {
 240         return grantEntries.removeElement(ge);
 241     }
 242 
 243     /**
 244      * Returns the (possibly expanded) keystore location, or null if the
 245      * expansion fails.
 246      */
 247     public String getKeyStoreUrl() {
 248         try {
 249             if (keyStoreUrlString!=null && keyStoreUrlString.length()!=0) {
 250                 return expand(keyStoreUrlString, true).replace
 251                                                 (File.separatorChar, '/');
 252             }
 253         } catch (PropertyExpander.ExpandException peee) {
 254             if (debug != null) {
 255                 debug.println(peee.toString());
 256             }
 257             return null;
 258         }
 259         return null;
 260     }
 261 
 262     public void setKeyStoreUrl(String url) {
 263         keyStoreUrlString = url;
 264     }
 265 
 266     public String getKeyStoreType() {
 267         return keyStoreType;
 268     }
 269 
 270     public void setKeyStoreType(String type) {
 271         keyStoreType = type;
 272     }
 273 
 274     public String getKeyStoreProvider() {
 275         return keyStoreProvider;
 276     }
 277 
 278     public void setKeyStoreProvider(String provider) {
 279         keyStoreProvider = provider;
 280     }
 281 
 282     public String getStorePassURL() {
 283         try {
 284             if (storePassURL!=null && storePassURL.length()!=0) {
 285                 return expand(storePassURL, true).replace
 286                                                 (File.separatorChar, '/');
 287             }
 288         } catch (PropertyExpander.ExpandException peee) {
 289             if (debug != null) {
 290                 debug.println(peee.toString());
 291             }
 292             return null;
 293         }
 294         return null;
 295     }
 296 
 297     public void setStorePassURL(String storePassURL) {
 298         this.storePassURL = storePassURL;
 299     }
 300 
 301     /**
 302      * Enumerate all the entries in the global policy object.
 303      * This method is used by policy admin tools.   The tools
 304      * should use the Enumeration methods on the returned object
 305      * to fetch the elements sequentially.
 306      */
 307     public Enumeration<GrantEntry> grantElements(){
 308         return grantEntries.elements();
 309     }
 310 
 311     public Collection<DomainEntry> getDomainEntries() {
 312         return domainEntries.values();
 313     }
 314 
 315     /**
 316      * write out the policy
 317      */
 318 
 319     public void write(Writer policy)
 320     {
 321         PrintWriter out = new PrintWriter(new BufferedWriter(policy));
 322 
 323         Enumeration<GrantEntry> enum_ = grantElements();
 324 
 325         out.println("/* AUTOMATICALLY GENERATED ON "+
 326                     (new java.util.Date()) + "*/");
 327         out.println("/* DO NOT EDIT */");
 328         out.println();
 329 
 330         // write the (unexpanded) keystore entry as the first entry of the
 331         // policy file
 332         if (keyStoreUrlString != null) {
 333             writeKeyStoreEntry(out);
 334         }
 335         if (storePassURL != null) {
 336             writeStorePassURL(out);
 337         }
 338 
 339         // write "grant" entries
 340         while (enum_.hasMoreElements()) {
 341             GrantEntry ge = enum_.nextElement();
 342             ge.write(out);
 343             out.println();
 344         }
 345         out.flush();
 346     }
 347 
 348     /**
 349      * parses a keystore entry
 350      */
 351     private void parseKeyStoreEntry() throws ParsingException, IOException {
 352         match("keystore");
 353         keyStoreUrlString = match("quoted string");
 354 
 355         // parse keystore type
 356         if (!peek(",")) {
 357             return; // default type
 358         }
 359         match(",");
 360 
 361         if (peek("\"")) {
 362             keyStoreType = match("quoted string");
 363         } else {
 364             throw new ParsingException(st.lineno(),
 365                 LocalizedMessage.getNonlocalized("expected.keystore.type"));
 366         }
 367 
 368         // parse keystore provider
 369         if (!peek(",")) {
 370             return; // provider optional
 371         }
 372         match(",");
 373 
 374         if (peek("\"")) {
 375             keyStoreProvider = match("quoted string");
 376         } else {
 377             throw new ParsingException(st.lineno(),
 378                 LocalizedMessage.getNonlocalized("expected.keystore.provider"));
 379         }
 380     }
 381 
 382     private void parseStorePassURL() throws ParsingException, IOException {
 383         match("keyStorePasswordURL");
 384         storePassURL = match("quoted string");
 385     }
 386 
 387     /**
 388      * writes the (unexpanded) keystore entry
 389      */
 390     private void writeKeyStoreEntry(PrintWriter out) {
 391         out.print("keystore \"");
 392         out.print(keyStoreUrlString);
 393         out.print('"');
 394         if (keyStoreType != null && !keyStoreType.isEmpty())
 395             out.print(", \"" + keyStoreType + "\"");
 396         if (keyStoreProvider != null && !keyStoreProvider.isEmpty())
 397             out.print(", \"" + keyStoreProvider + "\"");
 398         out.println(";");
 399         out.println();
 400     }
 401 
 402     private void writeStorePassURL(PrintWriter out) {
 403         out.print("keystorePasswordURL \"");
 404         out.print(storePassURL);
 405         out.print('"');
 406         out.println(";");
 407         out.println();
 408     }
 409 
 410     /**
 411      * parse a Grant entry
 412      */
 413     private GrantEntry parseGrantEntry()
 414         throws ParsingException, IOException
 415     {
 416         GrantEntry e = new GrantEntry();
 417         LinkedList<PrincipalEntry> principals = null;
 418         boolean ignoreEntry = false;
 419 
 420         match("grant");
 421 
 422         while(!peek("{")) {
 423 
 424             if (peekAndMatch("Codebase")) {
 425                 if (e.codeBase != null)
 426                     throw new ParsingException(
 427                             st.lineno(),
 428                             LocalizedMessage.getNonlocalized
 429                                 ("multiple.Codebase.expressions"));
 430                 e.codeBase = match("quoted string");
 431                 peekAndMatch(",");
 432             } else if (peekAndMatch("SignedBy")) {
 433                 if (e.signedBy != null)
 434                     throw new ParsingException(
 435                             st.lineno(),
 436                             LocalizedMessage.getNonlocalized
 437                                 ("multiple.SignedBy.expressions"));
 438                 e.signedBy = match("quoted string");
 439 
 440                 // verify syntax of the aliases
 441                 StringTokenizer aliases = new StringTokenizer(e.signedBy,
 442                                                               ",", true);
 443                 int actr = 0;
 444                 int cctr = 0;
 445                 while (aliases.hasMoreTokens()) {
 446                     String alias = aliases.nextToken().trim();
 447                     if (alias.equals(","))
 448                         cctr++;
 449                     else if (!alias.isEmpty())
 450                         actr++;
 451                 }
 452                 if (actr <= cctr)
 453                     throw new ParsingException(
 454                             st.lineno(),
 455                             LocalizedMessage.getNonlocalized
 456                                 ("SignedBy.has.empty.alias"));
 457 
 458                 peekAndMatch(",");
 459             } else if (peekAndMatch("Principal")) {
 460                 if (principals == null) {
 461                     principals = new LinkedList<>();
 462                 }
 463 
 464                 String principalClass;
 465                 String principalName;
 466 
 467                 if (peek("\"")) {
 468                     // both the principalClass and principalName
 469                     // will be replaced later
 470                     principalClass = PrincipalEntry.REPLACE_NAME;
 471                     principalName = match("principal type");
 472                 } else {
 473                     // check for principalClass wildcard
 474                     if (peek("*")) {
 475                         match("*");
 476                         principalClass = PrincipalEntry.WILDCARD_CLASS;
 477                     } else {
 478                         principalClass = match("principal type");
 479                     }
 480 
 481                     // check for principalName wildcard
 482                     if (peek("*")) {
 483                         match("*");
 484                         principalName = PrincipalEntry.WILDCARD_NAME;
 485                     } else {
 486                         principalName = match("quoted string");
 487                     }
 488 
 489                     // disallow WILDCARD_CLASS && actual name
 490                     if (principalClass.equals(PrincipalEntry.WILDCARD_CLASS) &&
 491                         !principalName.equals(PrincipalEntry.WILDCARD_NAME)) {
 492                         if (debug != null) {
 493                                 debug.println("disallowing principal that " +
 494                                     "has WILDCARD class but no WILDCARD name");
 495                         }
 496                         throw new ParsingException
 497                                 (st.lineno(),
 498                                 LocalizedMessage.getNonlocalized
 499                                     ("can.not.specify.Principal.with.a.wildcard.class.without.a.wildcard.name"));
 500                     }
 501                 }
 502 
 503                 try {
 504                     principalName = expand(principalName);
 505 
 506                     if (principalClass.equals
 507                                 ("javax.security.auth.x500.X500Principal") &&
 508                         !principalName.equals(PrincipalEntry.WILDCARD_NAME)) {
 509 
 510                         // 4702543:  X500 names with an EmailAddress
 511                         // were encoded incorrectly.  construct a new
 512                         // X500Principal with correct encoding.
 513 
 514                         X500Principal p = new X500Principal
 515                                 ((new X500Principal(principalName)).toString());
 516                         principalName = p.getName();
 517                     }
 518 
 519                     principals.add
 520                         (new PrincipalEntry(principalClass, principalName));
 521                 } catch (PropertyExpander.ExpandException peee) {
 522                     // ignore the entire policy entry
 523                     // but continue parsing all the info
 524                     // so we can get to the next entry
 525                     if (debug != null) {
 526                         debug.println("principal name expansion failed: " +
 527                                         principalName);
 528                     }
 529                     ignoreEntry = true;
 530                 }
 531                 peekAndMatch(",");
 532 
 533             } else {
 534                 throw new ParsingException(st.lineno(),
 535                     LocalizedMessage.getNonlocalized
 536                         ("expected.codeBase.or.SignedBy.or.Principal"));
 537             }
 538         }
 539 
 540         if (principals != null) e.principals = principals;
 541         match("{");
 542 
 543         while(!peek("}")) {
 544             if (peek("Permission")) {
 545                 try {
 546                     PermissionEntry pe = parsePermissionEntry();
 547                     e.add(pe);
 548                 } catch (PropertyExpander.ExpandException peee) {
 549                     // ignore. The add never happened
 550                     if (debug != null) {
 551                         debug.println(peee.toString());
 552                     }
 553                     skipEntry();  // BugId 4219343
 554                 }
 555                 match(";");
 556             } else {
 557                 throw new
 558                     ParsingException(st.lineno(),
 559                         LocalizedMessage.getNonlocalized
 560                             ("expected.permission.entry"));
 561             }
 562         }
 563         match("}");
 564 
 565         try {
 566             if (e.signedBy != null) e.signedBy = expand(e.signedBy);
 567             if (e.codeBase != null) {
 568                 e.codeBase = expand(e.codeBase, true).replace
 569                                     (File.separatorChar, '/');
 570             }
 571         } catch (PropertyExpander.ExpandException peee) {
 572             if (debug != null) {
 573                 debug.println(peee.toString());
 574             }
 575             return null;
 576         }
 577 
 578         return (ignoreEntry == true) ? null : e;
 579     }
 580 
 581     /**
 582      * parse a Permission entry
 583      */
 584     private PermissionEntry parsePermissionEntry()
 585         throws ParsingException, IOException, PropertyExpander.ExpandException
 586     {
 587         PermissionEntry e = new PermissionEntry();
 588 
 589         // Permission
 590         match("Permission");
 591         e.permission = match("permission type");
 592 
 593         if (peek("\"")) {
 594             // Permission name
 595             e.name = expand(match("quoted string"));
 596         }
 597 
 598         if (!peek(",")) {
 599             return e;
 600         }
 601         match(",");
 602 
 603         if (peek("\"")) {
 604                 e.action = expand(match("quoted string"));
 605                 if (!peek(",")) {
 606                     return e;
 607                 }
 608                 match(",");
 609         }
 610 
 611         if (peekAndMatch("SignedBy")) {
 612             e.signedBy = expand(match("quoted string"));
 613         }
 614         return e;
 615     }
 616 
 617     /**
 618      * parse a domain entry
 619      */
 620     private DomainEntry parseDomainEntry()
 621         throws ParsingException, IOException
 622     {
 623         boolean ignoreEntry = false;
 624         DomainEntry domainEntry;
 625         String name = null;
 626         Map<String, String> properties = new HashMap<>();
 627 
 628         match("domain");
 629         name = match("domain name");
 630 
 631         while(!peek("{")) {
 632             // get the domain properties
 633             properties = parseProperties("{");
 634         }
 635         match("{");
 636         domainEntry = new DomainEntry(name, properties);
 637 
 638         while(!peek("}")) {
 639 
 640             match("keystore");
 641             name = match("keystore name");
 642             // get the keystore properties
 643             if (!peek("}")) {
 644                 properties = parseProperties(";");
 645             }
 646             match(";");
 647             domainEntry.add(new KeyStoreEntry(name, properties));
 648         }
 649         match("}");
 650 
 651         return (ignoreEntry == true) ? null : domainEntry;
 652     }
 653 
 654     /*
 655      * Return a collection of domain properties or keystore properties.
 656      */
 657     private Map<String, String> parseProperties(String terminator)
 658         throws ParsingException, IOException {
 659 
 660         Map<String, String> properties = new HashMap<>();
 661         String key;
 662         String value;
 663         while (!peek(terminator)) {
 664             key = match("property name");
 665             match("=");
 666 
 667             try {
 668                 value = expand(match("quoted string"));
 669             } catch (PropertyExpander.ExpandException peee) {
 670                 throw new IOException(peee.getLocalizedMessage());
 671             }
 672             properties.put(key.toLowerCase(Locale.ENGLISH), value);
 673         }
 674 
 675         return properties;
 676     }
 677 
 678     private boolean peekAndMatch(String expect)
 679         throws ParsingException, IOException
 680     {
 681         if (peek(expect)) {
 682             match(expect);
 683             return true;
 684         } else {
 685             return false;
 686         }
 687     }
 688 
 689     private boolean peek(String expect) {
 690         boolean found = false;
 691 
 692         switch (lookahead) {
 693 
 694         case StreamTokenizer.TT_WORD:
 695             if (expect.equalsIgnoreCase(st.sval))
 696                 found = true;
 697             break;
 698         case ',':
 699             if (expect.equalsIgnoreCase(","))
 700                 found = true;
 701             break;
 702         case '{':
 703             if (expect.equalsIgnoreCase("{"))
 704                 found = true;
 705             break;
 706         case '}':
 707             if (expect.equalsIgnoreCase("}"))
 708                 found = true;
 709             break;
 710         case '"':
 711             if (expect.equalsIgnoreCase("\""))
 712                 found = true;
 713             break;
 714         case '*':
 715             if (expect.equalsIgnoreCase("*"))
 716                 found = true;
 717             break;
 718         case ';':
 719             if (expect.equalsIgnoreCase(";"))
 720                 found = true;
 721             break;
 722         default:
 723 
 724         }
 725         return found;
 726     }
 727 
 728     private String match(String expect)
 729         throws ParsingException, IOException
 730     {
 731         String value = null;
 732 
 733         switch (lookahead) {
 734         case StreamTokenizer.TT_NUMBER:
 735             throw new ParsingException(st.lineno(), expect,
 736                 LocalizedMessage.getNonlocalized("number.") +
 737                     String.valueOf(st.nval));
 738         case StreamTokenizer.TT_EOF:
 739             LocalizedMessage localizedMsg = new LocalizedMessage
 740                 ("expected.expect.read.end.of.file.");
 741             Object[] source = {expect};
 742             String msg = "expected [" + expect + "], read [end of file]";
 743             throw new ParsingException(msg, localizedMsg, source);
 744         case StreamTokenizer.TT_WORD:
 745             if (expect.equalsIgnoreCase(st.sval)) {
 746                 lookahead = st.nextToken();
 747             } else if (expect.equalsIgnoreCase("permission type")) {
 748                 value = st.sval;
 749                 lookahead = st.nextToken();
 750             } else if (expect.equalsIgnoreCase("principal type")) {
 751                 value = st.sval;
 752                 lookahead = st.nextToken();
 753             } else if (expect.equalsIgnoreCase("domain name") ||
 754                        expect.equalsIgnoreCase("keystore name") ||
 755                        expect.equalsIgnoreCase("property name")) {
 756                 value = st.sval;
 757                 lookahead = st.nextToken();
 758             } else {
 759                  throw new ParsingException(st.lineno(), expect,
 760                                             st.sval);
 761             }
 762             break;
 763         case '"':
 764             if (expect.equalsIgnoreCase("quoted string")) {
 765                 value = st.sval;
 766                 lookahead = st.nextToken();
 767             } else if (expect.equalsIgnoreCase("permission type")) {
 768                 value = st.sval;
 769                 lookahead = st.nextToken();
 770             } else if (expect.equalsIgnoreCase("principal type")) {
 771                 value = st.sval;
 772                 lookahead = st.nextToken();
 773             } else {
 774                 throw new ParsingException(st.lineno(), expect, st.sval);
 775             }
 776             break;
 777         case ',':
 778             if (expect.equalsIgnoreCase(","))
 779                 lookahead = st.nextToken();
 780             else
 781                 throw new ParsingException(st.lineno(), expect, ",");
 782             break;
 783         case '{':
 784             if (expect.equalsIgnoreCase("{"))
 785                 lookahead = st.nextToken();
 786             else
 787                 throw new ParsingException(st.lineno(), expect, "{");
 788             break;
 789         case '}':
 790             if (expect.equalsIgnoreCase("}"))
 791                 lookahead = st.nextToken();
 792             else
 793                 throw new ParsingException(st.lineno(), expect, "}");
 794             break;
 795         case ';':
 796             if (expect.equalsIgnoreCase(";"))
 797                 lookahead = st.nextToken();
 798             else
 799                 throw new ParsingException(st.lineno(), expect, ";");
 800             break;
 801         case '*':
 802             if (expect.equalsIgnoreCase("*"))
 803                 lookahead = st.nextToken();
 804             else
 805                 throw new ParsingException(st.lineno(), expect, "*");
 806             break;
 807         case '=':
 808             if (expect.equalsIgnoreCase("="))
 809                 lookahead = st.nextToken();
 810             else
 811                 throw new ParsingException(st.lineno(), expect, "=");
 812             break;
 813         default:
 814             throw new ParsingException(st.lineno(), expect,
 815                                new String(new char[] {(char)lookahead}));
 816         }
 817         return value;
 818     }
 819 
 820     /**
 821      * skip all tokens for this entry leaving the delimiter ";"
 822      * in the stream.
 823      */
 824     private void skipEntry() throws ParsingException, IOException {
 825         while(lookahead != ';') {
 826             switch (lookahead) {
 827             case StreamTokenizer.TT_NUMBER:
 828                 throw new ParsingException(st.lineno(), ";",
 829                         LocalizedMessage.getNonlocalized("number.") +
 830                             String.valueOf(st.nval));
 831             case StreamTokenizer.TT_EOF:
 832                 throw new ParsingException(LocalizedMessage.getNonlocalized
 833                         ("expected.read.end.of.file."));
 834             default:
 835                 lookahead = st.nextToken();
 836             }
 837         }
 838     }
 839 
 840     /**
 841      * Each grant entry in the policy configuration file is
 842      * represented by a GrantEntry object.
 843      *
 844      * <p>
 845      * For example, the entry
 846      * <pre>
 847      *      grant signedBy "Duke" {
 848      *          permission java.io.FilePermission "/tmp", "read,write";
 849      *      };
 850      *
 851      * </pre>
 852      * is represented internally
 853      * <pre>
 854      *
 855      * pe = new PermissionEntry("java.io.FilePermission",
 856      *                           "/tmp", "read,write");
 857      *
 858      * ge = new GrantEntry("Duke", null);
 859      *
 860      * ge.add(pe);
 861      *
 862      * </pre>
 863      *
 864      * @author Roland Schemers
 865      *
 866      * version 1.19, 05/21/98
 867      */
 868 
 869     public static class GrantEntry {
 870 
 871         public String signedBy;
 872         public String codeBase;
 873         public LinkedList<PrincipalEntry> principals;
 874         public Vector<PermissionEntry> permissionEntries;
 875 
 876         public GrantEntry() {
 877             principals = new LinkedList<PrincipalEntry>();
 878             permissionEntries = new Vector<PermissionEntry>();
 879         }
 880 
 881         public GrantEntry(String signedBy, String codeBase) {
 882             this.codeBase = codeBase;
 883             this.signedBy = signedBy;
 884             principals = new LinkedList<PrincipalEntry>();
 885             permissionEntries = new Vector<PermissionEntry>();
 886         }
 887 
 888         public void add(PermissionEntry pe)
 889         {
 890             permissionEntries.addElement(pe);
 891         }
 892 
 893         public boolean remove(PrincipalEntry pe)
 894         {
 895             return principals.remove(pe);
 896         }
 897 
 898         public boolean remove(PermissionEntry pe)
 899         {
 900             return permissionEntries.removeElement(pe);
 901         }
 902 
 903         public boolean contains(PrincipalEntry pe)
 904         {
 905             return principals.contains(pe);
 906         }
 907 
 908         public boolean contains(PermissionEntry pe)
 909         {
 910             return permissionEntries.contains(pe);
 911         }
 912 
 913         /**
 914          * Enumerate all the permission entries in this GrantEntry.
 915          */
 916         public Enumeration<PermissionEntry> permissionElements(){
 917             return permissionEntries.elements();
 918         }
 919 
 920 
 921         public void write(PrintWriter out) {
 922             out.print("grant");
 923             if (signedBy != null) {
 924                 out.print(" signedBy \"");
 925                 out.print(signedBy);
 926                 out.print('"');
 927                 if (codeBase != null)
 928                     out.print(", ");
 929             }
 930             if (codeBase != null) {
 931                 out.print(" codeBase \"");
 932                 out.print(codeBase);
 933                 out.print('"');
 934                 if (principals != null && principals.size() > 0)
 935                     out.print(",\n");
 936             }
 937             if (principals != null && principals.size() > 0) {
 938                 Iterator<PrincipalEntry> pli = principals.iterator();
 939                 while (pli.hasNext()) {
 940                     out.print("      ");
 941                     PrincipalEntry pe = pli.next();
 942                     pe.write(out);
 943                     if (pli.hasNext())
 944                         out.print(",\n");
 945                 }
 946             }
 947             out.println(" {");
 948             Enumeration<PermissionEntry> enum_ = permissionEntries.elements();
 949             while (enum_.hasMoreElements()) {
 950                 PermissionEntry pe = enum_.nextElement();
 951                 out.write("  ");
 952                 pe.write(out);
 953             }
 954             out.println("};");
 955         }
 956 
 957         public Object clone() {
 958             GrantEntry ge = new GrantEntry();
 959             ge.codeBase = this.codeBase;
 960             ge.signedBy = this.signedBy;
 961             ge.principals = new LinkedList<PrincipalEntry>(this.principals);
 962             ge.permissionEntries =
 963                         new Vector<PermissionEntry>(this.permissionEntries);
 964             return ge;
 965         }
 966     }
 967 
 968     /**
 969      * Principal info (class and name) in a grant entry
 970      */
 971     public static class PrincipalEntry implements Principal {
 972 
 973         public static final String WILDCARD_CLASS = "WILDCARD_PRINCIPAL_CLASS";
 974         public static final String WILDCARD_NAME = "WILDCARD_PRINCIPAL_NAME";
 975         public static final String REPLACE_NAME = "PolicyParser.REPLACE_NAME";
 976 
 977         String principalClass;
 978         String principalName;
 979 
 980         /**
 981          * A PrincipalEntry consists of the Principal class and Principal name.
 982          *
 983          * @param principalClass the Principal class
 984          * @param principalName the Principal name
 985          * @throws NullPointerException if principalClass or principalName
 986          *                              are null
 987          */
 988         public PrincipalEntry(String principalClass, String principalName) {
 989             if (principalClass == null || principalName == null)
 990                 throw new NullPointerException(LocalizedMessage.getNonlocalized
 991                     ("null.principalClass.or.principalName"));
 992             this.principalClass = principalClass;
 993             this.principalName = principalName;
 994         }
 995 
 996         boolean isWildcardName() {
 997             return principalName.equals(WILDCARD_NAME);
 998         }
 999 
1000         boolean isWildcardClass() {
1001             return principalClass.equals(WILDCARD_CLASS);
1002         }
1003 
1004         boolean isReplaceName() {
1005             return principalClass.equals(REPLACE_NAME);
1006         }
1007 
1008         public String getPrincipalClass() {
1009             return principalClass;
1010         }
1011 
1012         public String getPrincipalName() {
1013             return principalName;
1014         }
1015 
1016         public String getDisplayClass() {
1017             if (isWildcardClass()) {
1018                 return "*";
1019             } else if (isReplaceName()) {
1020                 return "";
1021             }
1022             else return principalClass;
1023         }
1024 
1025         public String getDisplayName() {
1026             return getDisplayName(false);
1027         }
1028 
1029         public String getDisplayName(boolean addQuote) {
1030             if (isWildcardName()) {
1031                 return "*";
1032             }
1033             else {
1034                 if (addQuote) return "\"" + principalName + "\"";
1035                 else return principalName;
1036             }
1037         }
1038 
1039         @Override
1040         public String getName() {
1041             return principalName;
1042         }
1043 
1044         @Override
1045         public String toString() {
1046             if (!isReplaceName()) {
1047                 return getDisplayClass() + "/" + getDisplayName();
1048             } else {
1049                 return getDisplayName();
1050             }
1051         }
1052 
1053         /**
1054          * Test for equality between the specified object and this object.
1055          * Two PrincipalEntries are equal if their class and name values
1056          * are equal.
1057          *
1058          * @param obj the object to test for equality with this object
1059          * @return true if the objects are equal, false otherwise
1060          */
1061         @Override
1062         public boolean equals(Object obj) {
1063             if (this == obj)
1064                 return true;
1065 
1066             if (!(obj instanceof PrincipalEntry))
1067                 return false;
1068 
1069             PrincipalEntry that = (PrincipalEntry)obj;
1070             return (principalClass.equals(that.principalClass) &&
1071                     principalName.equals(that.principalName));
1072         }
1073 
1074         /**
1075          * Return a hashcode for this PrincipalEntry.
1076          *
1077          * @return a hashcode for this PrincipalEntry
1078          */
1079         @Override
1080         public int hashCode() {
1081             return principalClass.hashCode();
1082         }
1083 
1084         public void write(PrintWriter out) {
1085             out.print("principal " + getDisplayClass() + " " +
1086                       getDisplayName(true));
1087         }
1088     }
1089 
1090     /**
1091      * Each permission entry in the policy configuration file is
1092      * represented by a
1093      * PermissionEntry object.
1094      *
1095      * <p>
1096      * For example, the entry
1097      * <pre>
1098      *          permission java.io.FilePermission "/tmp", "read,write";
1099      * </pre>
1100      * is represented internally
1101      * <pre>
1102      *
1103      * pe = new PermissionEntry("java.io.FilePermission",
1104      *                           "/tmp", "read,write");
1105      * </pre>
1106      *
1107      * @author Roland Schemers
1108      *
1109      * version 1.19, 05/21/98
1110      */
1111 
1112     public static class PermissionEntry {
1113 
1114         public String permission;
1115         public String name;
1116         public String action;
1117         public String signedBy;
1118 
1119         public PermissionEntry() {
1120         }
1121 
1122         public PermissionEntry(String permission,
1123                         String name,
1124                         String action) {
1125             this.permission = permission;
1126             this.name = name;
1127             this.action = action;
1128         }
1129 
1130         /**
1131          * Calculates a hash code value for the object.  Objects
1132          * which are equal will also have the same hashcode.
1133          */
1134         @Override
1135         public int hashCode() {
1136             int retval = permission.hashCode();
1137             if (name != null) retval ^= name.hashCode();
1138             if (action != null) retval ^= action.hashCode();
1139             return retval;
1140         }
1141 
1142         @Override
1143         public boolean equals(Object obj) {
1144             if (obj == this)
1145                 return true;
1146 
1147             if (! (obj instanceof PermissionEntry))
1148                 return false;
1149 
1150             PermissionEntry that = (PermissionEntry) obj;
1151 
1152             if (this.permission == null) {
1153                 if (that.permission != null) return false;
1154             } else {
1155                 if (!this.permission.equals(that.permission)) return false;
1156             }
1157 
1158             if (this.name == null) {
1159                 if (that.name != null) return false;
1160             } else {
1161                 if (!this.name.equals(that.name)) return false;
1162             }
1163 
1164             if (this.action == null) {
1165                 if (that.action != null) return false;
1166             } else {
1167                 if (!this.action.equals(that.action)) return false;
1168             }
1169 
1170             if (this.signedBy == null) {
1171                 if (that.signedBy != null) return false;
1172             } else {
1173                 if (!this.signedBy.equals(that.signedBy)) return false;
1174             }
1175 
1176             // everything matched -- the 2 objects are equal
1177             return true;
1178         }
1179 
1180         public void write(PrintWriter out) {
1181             out.print("permission ");
1182             out.print(permission);
1183             if (name != null) {
1184                 out.print(" \"");
1185 
1186                 // ATTENTION: regex with double escaping,
1187                 // the normal forms look like:
1188                 // $name =~ s/\\/\\\\/g; and
1189                 // $name =~ s/\"/\\\"/g;
1190                 // and then in a java string, it's escaped again
1191 
1192                 out.print(name.replaceAll("\\\\", "\\\\\\\\").replaceAll("\\\"", "\\\\\\\""));
1193                 out.print('"');
1194             }
1195             if (action != null) {
1196                 out.print(", \"");
1197                 out.print(action);
1198                 out.print('"');
1199             }
1200             if (signedBy != null) {
1201                 out.print(", signedBy \"");
1202                 out.print(signedBy);
1203                 out.print('"');
1204             }
1205             out.println(";");
1206         }
1207     }
1208 
1209     /**
1210      * Each domain entry in the keystore domain configuration file is
1211      * represented by a DomainEntry object.
1212      */
1213     static class DomainEntry {
1214         private final String name;
1215         private final Map<String, String> properties;
1216         private final Map<String, KeyStoreEntry> entries;
1217 
1218         DomainEntry(String name, Map<String, String> properties) {
1219             this.name = name;
1220             this.properties = properties;
1221             entries = new HashMap<>();
1222         }
1223 
1224         String getName() {
1225             return name;
1226         }
1227 
1228         Map<String, String> getProperties() {
1229             return properties;
1230         }
1231 
1232         Collection<KeyStoreEntry> getEntries() {
1233             return entries.values();
1234         }
1235 
1236         void add(KeyStoreEntry entry) throws ParsingException {
1237             String keystoreName = entry.getName();
1238             if (!entries.containsKey(keystoreName)) {
1239                 entries.put(keystoreName, entry);
1240             } else {
1241                 LocalizedMessage localizedMsg = new LocalizedMessage
1242                     ("duplicate.keystore.name");
1243                 Object[] source = {keystoreName};
1244                 String msg = "duplicate keystore name: " + keystoreName;
1245                 throw new ParsingException(msg, localizedMsg, source);
1246             }
1247         }
1248 
1249         @Override
1250         public String toString() {
1251             StringBuilder s =
1252                 new StringBuilder("\ndomain ").append(name);
1253 
1254             if (properties != null) {
1255                 for (Map.Entry<String, String> property :
1256                     properties.entrySet()) {
1257                     s.append("\n        ").append(property.getKey()).append('=')
1258                         .append(property.getValue());
1259                 }
1260             }
1261             s.append(" {\n");
1262 
1263             if (entries != null) {
1264                 for (KeyStoreEntry entry : entries.values()) {
1265                     s.append(entry).append("\n");
1266                 }
1267             }
1268             s.append("}");
1269 
1270             return s.toString();
1271         }
1272     }
1273 
1274     /**
1275      * Each keystore entry in the keystore domain configuration file is
1276      * represented by a KeyStoreEntry object.
1277      */
1278 
1279     static class KeyStoreEntry {
1280         private final String name;
1281         private final Map<String, String> properties;
1282 
1283         KeyStoreEntry(String name, Map<String, String> properties) {
1284             this.name = name;
1285             this.properties = properties;
1286         }
1287 
1288         String getName() {
1289             return name;
1290         }
1291 
1292         Map<String, String>  getProperties() {
1293             return properties;
1294         }
1295 
1296         @Override
1297         public String toString() {
1298             StringBuilder s = new StringBuilder("\n    keystore ").append(name);
1299             if (properties != null) {
1300                 for (Map.Entry<String, String> property :
1301                     properties.entrySet()) {
1302                     s.append("\n        ").append(property.getKey()).append('=')
1303                         .append(property.getValue());
1304                 }
1305             }
1306             s.append(";");
1307 
1308             return s.toString();
1309         }
1310     }
1311 
1312     public static class ParsingException extends GeneralSecurityException {
1313 
1314         private static final long serialVersionUID = -4330692689482574072L;
1315 
1316         private String i18nMessage;
1317         private LocalizedMessage localizedMsg;
1318         private Object[] source;
1319 
1320         /**
1321          * Constructs a ParsingException with the specified
1322          * detail message. A detail message is a String that describes
1323          * this particular exception, which may, for example, specify which
1324          * algorithm is not available.
1325          *
1326          * @param msg the detail message.
1327          */
1328         public ParsingException(String msg) {
1329             super(msg);
1330             i18nMessage = msg;
1331         }
1332 
1333         public ParsingException(String msg, LocalizedMessage localizedMsg,
1334                                 Object[] source) {
1335             super(msg);
1336             this.localizedMsg = localizedMsg;
1337             this.source = source;
1338         }
1339 
1340         public ParsingException(int line, String msg) {
1341             super("line " + line + ": " + msg);
1342             localizedMsg = new LocalizedMessage("line.number.msg");
1343             source = new Object[] {line, msg};
1344         }
1345 
1346         public ParsingException(int line, String expect, String actual) {
1347             super("line " + line + ": expected [" + expect +
1348                 "], found [" + actual + "]");
1349             localizedMsg = new LocalizedMessage
1350                 ("line.number.expected.expect.found.actual.");
1351             source = new Object[] {line, expect, actual};
1352         }
1353 
1354         public String getNonlocalizedMessage() {
1355             return i18nMessage != null ? i18nMessage :
1356                 localizedMsg.formatNonlocalized(source);
1357         }
1358     }
1359 
1360     public static void main(String[] arg) throws Exception {
1361         try (FileReader fr = new FileReader(arg[0]);
1362              FileWriter fw = new FileWriter(arg[1])) {
1363             PolicyParser pp = new PolicyParser(true);
1364             pp.read(fr);
1365             pp.write(fw);
1366         }
1367     }
1368 }