1/*
2 * Copyright (c) 1997, 2017, 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
26package sun.security.provider;
27
28import java.io.*;
29import java.security.GeneralSecurityException;
30import java.security.Principal;
31import java.util.*;
32import javax.security.auth.x500.X500Principal;
33
34import sun.security.util.Debug;
35import sun.security.util.PropertyExpander;
36import 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
80public 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 =
209                            new LocalizedMessage("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.getMessage
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.getMessage("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.getMessage("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.length() > 0)
395            out.print(", \"" + keyStoreType + "\"");
396        if (keyStoreProvider != null && keyStoreProvider.length() > 0)
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.getMessage
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.getMessage
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.length() > 0)
450                        actr++;
451                }
452                if (actr <= cctr)
453                    throw new ParsingException(
454                            st.lineno(),
455                            LocalizedMessage.getMessage
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.getMessage
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.getMessage
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.getMessage
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.getMessage("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.getMessage("number.") +
830                            String.valueOf(st.nval));
831            case StreamTokenizer.TT_EOF:
832                throw new ParsingException(LocalizedMessage.getMessage
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.getMessage
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            // don't call form.format unless getLocalizedMessage is called
1343            // to avoid unnecessary permission checks
1344            localizedMsg = new LocalizedMessage("line.number.msg");
1345            source = new Object[] {line, msg};
1346        }
1347
1348        public ParsingException(int line, String expect, String actual) {
1349            super("line " + line + ": expected [" + expect +
1350                "], found [" + actual + "]");
1351            // don't call form.format unless getLocalizedMessage is called
1352            // to avoid unnecessary permission checks
1353            localizedMsg = new LocalizedMessage
1354                ("line.number.expected.expect.found.actual.");
1355            source = new Object[] {line, expect, actual};
1356        }
1357
1358        @Override
1359        public String getLocalizedMessage() {
1360            return i18nMessage != null ? i18nMessage : localizedMsg.format(source);
1361        }
1362    }
1363
1364    public static void main(String[] arg) throws Exception {
1365        try (FileReader fr = new FileReader(arg[0]);
1366             FileWriter fw = new FileWriter(arg[1])) {
1367            PolicyParser pp = new PolicyParser(true);
1368            pp.read(fr);
1369            pp.write(fw);
1370        }
1371    }
1372}
1373