001    package sysModel;
002    
003    /* PolicyFile.java -- policy file reader.
004    Copyright (C) 2003  Casey Marshall <rsdio@metastatic.org>
005    
006    This program is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    This program is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with this program; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
019    02111-1307 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    // package gnu.java.security;
040    
041    import java.io.File;
042    import java.io.IOException;
043    import java.io.InputStreamReader;
044    import java.io.StreamTokenizer;
045    import java.lang.reflect.Constructor;
046    import java.net.MalformedURLException;
047    import java.net.URL;
048    import java.security.*;
049    import java.security.cert.Certificate;
050    import java.security.cert.X509Certificate;
051    import java.util.*;
052    
053    /**
054     * An implementation of a {@link Policy} object whose permissions are specified by a <em>policy
055     * file</em>.
056     * <p/>
057     * <p>The approximate syntax of policy files is:</p>
058     * <p/>
059     * <pre>
060     * policyFile ::= keystoreOrGrantEntries ;
061     * <p/>
062     * keystoreOrGrantEntries ::= keystoreOrGrantEntry |
063     *                            keystoreOrGrantEntries keystoreOrGrantEntry |
064     *                            EMPTY ;
065     * <p/>
066     * keystoreOrGrantEntry ::= keystoreEntry | grantEntry ;
067     * <p/>
068     * keystoreEntry ::= "keystore" keystoreUrl ';' |
069     *                   "keystore" keystoreUrl ',' keystoreAlgorithm ';' ;
070     * <p/>
071     * keystoreUrl ::= URL ;
072     * keystoreAlgorithm ::= STRING ;
073     * <p/>
074     * grantEntry ::= "grant" domainParameters '{' permissions '}' ';'
075     * <p/>
076     * domainParameters ::= domainParameter |
077     *                      domainParameter ',' domainParameters ;
078     * <p/>
079     * domainParameter ::= "signedBy" signerNames |
080     *                     "codeBase" codeBaseUrl |
081     *                     "principal" principalClassName principalName |
082     *                     "principal" principalName ;
083     * <p/>
084     * signerNames ::= quotedString ;
085     * codeBaseUrl ::= URL ;
086     * principalClassName ::= STRING ;
087     * principalName ::= quotedString ;
088     * <p/>
089     * quotedString ::= quoteChar STRING quoteChar ;
090     * quoteChar ::= '"' | '\'';
091     * <p/>
092     * permissions ::= permission | permissions permission ;
093     * <p/>
094     * permission ::= "permission" permissionClassName permissionTarget permissionAction |
095     *                "permission" permissionClassName permissionTarget |
096     *                "permission" permissionClassName;
097     * </pre>
098     * <p/>
099     * <p>Comments are either form of Java comments. Keystore entries only affect subsequent grant entries, so if a grant
100     * entry preceeds a keystore entry, that grant entry is not affected by that keystore entry. Certian instances of
101     * <code>${property-name}</code> will be replaced with <code>System.getProperty("property-name")</code> in quoted
102     * strings.</p>
103     * <p/>
104     * <p>This class will load the following files when created or refreshed, in order:</p>
105     * <p/>
106     * <ol> <li>The file <code>${java.home}/lib/security/java.policy</code>.</li> <li>All URLs specified by security
107     * properties <code>"policy.file.<i>n</i>"</code>, for increasing <i>n</i> starting from 1. The sequence stops at the
108     * first undefined property, so you must set <code>"policy.file.1"</code> if you also set <code>"policy.file.2"</code>,
109     * and so on.</li> <li>The URL specified by the property <code>"java.security.policy"</code>.</li> </ol>
110     *
111     * @author Casey Marshall (rsdio@metastatic.org)
112     * @see Policy
113     */
114    public final class PolicyFile extends Policy {
115    
116        // Constants and fields.
117        // -------------------------------------------------------------------------
118    
119        private static final boolean DEBUG = false; // = true;
120    
121        private static void debug(String msg) {
122            if (DEBUG) {
123                System.err.print(">> PolicyFile message: ");
124                System.err.println(msg);
125            }
126        }
127    
128        private static void debug(Throwable t) {
129            if (DEBUG) {
130                System.err.println(">> PolicyFile throws: ");
131                t.printStackTrace(System.err);
132            }
133        }
134    
135        private static final String DEFAULT_POLICY = System.getProperty("java.home")
136            + System.getProperty("file.separator") + "lib"
137            + System.getProperty("file.separator") + "security"
138            + System.getProperty("file.separator") + "java.policy";
139    
140        private final Map<CodeSource,Permissions> cs2pc;
141    
142        // Constructors.
143        // -------------------------------------------------------------------------
144    
145        public PolicyFile() {
146            cs2pc = new HashMap<CodeSource,Permissions>();
147            refresh();
148        }
149    
150        // Instance methods.
151        // -------------------------------------------------------------------------
152    
153        public PermissionCollection getPermissions(CodeSource codeSource) {
154            Permissions perms = new Permissions();
155            for(Iterator it = cs2pc.entrySet().iterator(); it.hasNext();) {
156                Map.Entry e = (Map.Entry)it.next();
157                CodeSource cs = (CodeSource)e.getKey();
158                if (cs.implies(codeSource)) {
159                    debug(cs + " -> " + codeSource);
160                    PermissionCollection pc = (PermissionCollection)e.getValue();
161                    for(Enumeration ee = pc.elements(); ee.hasMoreElements();) {
162                        perms.add((Permission)ee.nextElement());
163                    }
164                }
165                else {
166                    debug(cs + " !-> " + codeSource);
167                }
168            }
169            perms.setReadOnly();
170            return perms;
171        }
172    
173        public void refresh() {
174            cs2pc.clear();
175            List<URL> policyFiles = new LinkedList<URL>();
176            try {
177                policyFiles.add(new File(DEFAULT_POLICY).toURL());
178                policyFiles.addAll(AccessController.doPrivileged(new PrivilegedExceptionAction<List<URL>>() {
179                    public List<URL> run() throws Exception {
180                        LinkedList<URL> l = new LinkedList<URL>();
181                        for(int i = 1; ; i++) {
182                            String s = Security.getProperty("policy.file." + i);
183                            debug("policy.file." + i + '=' + s);
184                            if (null == s) {
185                                break;
186                            }
187                            l.add(new URL(s));
188                        }
189                        String s = System.getProperty("java.security.policy");
190                        debug("java.security.policy=" + s);
191                        if (null != s) {
192                            l.add(new URL(s));
193                        }
194                        return l;
195                    }
196                }));
197            }
198            catch(PrivilegedActionException pae) {
199                debug(pae);
200            }
201            catch(MalformedURLException mue) {
202                debug(mue);
203            }
204            for(Iterator it = policyFiles.iterator(); it.hasNext();) {
205                try {
206                    URL url = (URL)it.next();
207                    parse(url);
208                }
209                catch(IOException ioe) {
210                    debug(ioe);
211                }
212            }
213        }
214    
215        public String toString() {
216            return super.toString() + " [ " + cs2pc.toString() + " ]";
217        }
218    
219        // Own methods.
220        // -------------------------------------------------------------------------
221    
222        private static final int STATE_BEGIN = 0;
223        private static final int STATE_GRANT = 1;
224        private static final int STATE_PERMS = 2;
225    
226        /**
227         * Parse a policy file, incorporating the permission definitions described therein.
228         *
229         * @param url The URL of the policy file to read.
230         *
231         * @throws IOException if an I/O error occurs, or if the policy file cannot be parsed.
232         */
233        private void parse(URL url) throws IOException {
234            StreamTokenizer in = new StreamTokenizer(new InputStreamReader(url.openStream()));
235            in.resetSyntax();
236            in.slashSlashComments(true);
237            in.slashStarComments(true);
238            in.wordChars('A', 'Z');
239            in.wordChars('a', 'z');
240            in.wordChars('0', '9');
241            in.wordChars('.', '.');
242            in.wordChars('_', '_');
243            in.wordChars('$', '$');
244            in.whitespaceChars(' ', ' ');
245            in.whitespaceChars('\t', '\t');
246            in.whitespaceChars('\f', '\f');
247            in.whitespaceChars('\n', '\n');
248            in.whitespaceChars('\r', '\r');
249            in.quoteChar('\'');
250            in.quoteChar('"');
251    
252            int tok;
253            int state = STATE_BEGIN;
254            List<KeyStore> keystores = new LinkedList<KeyStore>();
255            URL currentBase = null;
256            List<Certificate> currentCerts = new LinkedList<Certificate>();
257            Permissions currentPerms = new Permissions();
258            while(StreamTokenizer.TT_EOF != (tok = in.nextToken())) {
259                switch(tok) {
260                    case '{':
261                        if (STATE_GRANT != state) {
262                            error(url, in, "spurious '{'");
263                        }
264                        state = STATE_PERMS;
265                        tok = in.nextToken();
266                        break;
267                    case '}':
268                        if (STATE_PERMS != state) {
269                            error(url, in, "spurious '}'");
270                        }
271                        state = STATE_BEGIN;
272                        currentPerms.setReadOnly();
273                        Certificate[] c = null;
274                        if (!currentCerts.isEmpty()) {
275                            c = (Certificate[])currentCerts.toArray(new Certificate[currentCerts.size()]);
276                        }
277                        cs2pc.put(new CodeSource(currentBase, c), currentPerms);
278                        currentCerts.clear();
279                        currentPerms = new Permissions();
280                        currentBase = null;
281                        tok = in.nextToken();
282                        if (';' != tok) {
283                            in.pushBack();
284                        }
285                        continue;
286                }
287                if (StreamTokenizer.TT_WORD != tok) {
288                    error(url, in, "expecting word token");
289                }
290    
291                // keystore "<keystore-path>" [',' "<keystore-type>"] ';'
292                if ("keystore".equalsIgnoreCase(in.sval)) {
293                    String alg = KeyStore.getDefaultType();
294                    tok = in.nextToken();
295                    if ('"' != tok && '\'' != tok) {
296                        error(url, in, "expecting key store URL");
297                    }
298                    String store = in.sval;
299                    tok = in.nextToken();
300                    if (',' == tok) {
301                        tok = in.nextToken();
302                        if ('"' != tok && '\'' != tok) {
303                            error(url, in, "expecting key store type");
304                        }
305                        alg = in.sval;
306                        tok = in.nextToken();
307                    }
308                    if (';' != tok) {
309                        error(url, in, "expecting semicolon");
310                    }
311                    try {
312                        KeyStore keystore = KeyStore.getInstance(alg);
313                        keystore.load(new URL(url, store).openStream(), null);
314                        keystores.add(keystore);
315                    }
316                    catch(Exception x) {
317                        error(url, in, x.toString());
318                    }
319                }
320                else if ("grant".equalsIgnoreCase(in.sval)) {
321                    if (STATE_BEGIN != state) {
322                        error(url, in, "extraneous grant keyword");
323                    }
324                    state = STATE_GRANT;
325                }
326                else if ("signedBy".equalsIgnoreCase(in.sval)) {
327                    if (STATE_GRANT != state && STATE_PERMS != state) {
328                        error(url, in, "spurious 'signedBy'");
329                    }
330                    if (keystores.isEmpty()) {
331                        error(url, in, "'signedBy' with no keystores");
332                    }
333                    tok = in.nextToken();
334                    if ('"' != tok && '\'' != tok) {
335                        error(url, in, "expecting signedBy name");
336                    }
337                    StringTokenizer st = new StringTokenizer(in.sval, ",");
338                    while(st.hasMoreTokens()) {
339                        String alias = st.nextToken();
340                        for(Iterator it = keystores.iterator(); it.hasNext();) {
341                            KeyStore keystore = (KeyStore)it.next();
342                            try {
343                                if (keystore.isCertificateEntry(alias)) {
344                                    currentCerts.add(keystore.getCertificate(alias));
345                                }
346                            }
347                            catch(KeyStoreException kse) {
348                                error(url, in, kse.toString());
349                            }
350                        }
351                    }
352                    tok = in.nextToken();
353                    if (',' != tok) {
354                        if (STATE_GRANT != state) {
355                            error(url, in, "spurious ','");
356                        }
357                        in.pushBack();
358                    }
359                }
360                else if ("codeBase".equalsIgnoreCase(in.sval)) {
361                    if (STATE_GRANT != state) {
362                        error(url, in, "spurious 'codeBase'");
363                    }
364                    tok = in.nextToken();
365                    if ('"' != tok && '\'' != tok) {
366                        error(url, in, "expecting code base URL");
367                    }
368                    String base = expand(in.sval);
369                    if ('/' != File.separatorChar) {
370                        base = base.replace(File.separatorChar, '/');
371                    }
372                    try {
373                        currentBase = new URL(base);
374                    }
375                    catch(MalformedURLException mue) {
376                        error(url, in, mue.toString());
377                    }
378                    tok = in.nextToken();
379                    if (',' != tok) {
380                        in.pushBack();
381                    }
382                }
383                else if ("principal".equalsIgnoreCase(in.sval)) {
384                    if (STATE_GRANT != state) {
385                        error(url, in, "spurious 'principal'");
386                    }
387                    tok = in.nextToken();
388                    if (StreamTokenizer.TT_WORD == tok) {
389                        tok = in.nextToken();
390                        if ('"' != tok && '\'' != tok) {
391                            error(url, in, "expecting principal name");
392                        }
393                        String name = in.sval;
394                        Principal p = null;
395                        try {
396                            Class pclass = Class.forName(in.sval);
397                            Constructor c =
398                                pclass.getConstructor(new Class[]{String.class});
399                            p = (Principal)c.newInstance(new Object[]{name});
400                        }
401                        catch(Exception x) {
402                            error(url, in, x.toString());
403                        }
404                        for(Iterator it = keystores.iterator(); it.hasNext();) {
405                            KeyStore ks = (KeyStore)it.next();
406                            try {
407                                for(Enumeration e = ks.aliases(); e.hasMoreElements();) {
408                                    String alias = (String)e.nextElement();
409                                    if (ks.isCertificateEntry(alias)) {
410                                        Certificate cert = ks.getCertificate(alias);
411                                        if (!(cert instanceof X509Certificate)) {
412                                            continue;
413                                        }
414                                        if (p.equals(((X509Certificate)cert).getSubjectDN()) ||
415                                            p.equals(((X509Certificate)cert).getSubjectX500Principal())) {
416                                            currentCerts.add(cert);
417                                        }
418                                    }
419                                }
420                            }
421                            catch(KeyStoreException kse) {
422                                error(url, in, kse.toString());
423                            }
424                        }
425                    }
426                    else if ('"' == tok || '\'' == tok) {
427                        String alias = in.sval;
428                        for(Iterator it = keystores.iterator(); it.hasNext();) {
429                            KeyStore ks = (KeyStore)it.next();
430                            try {
431                                if (ks.isCertificateEntry(alias)) {
432                                    currentCerts.add(ks.getCertificate(alias));
433                                }
434                            }
435                            catch(KeyStoreException kse) {
436                                error(url, in, kse.toString());
437                            }
438                        }
439                    }
440                    else {
441                        error(url, in, "expecting principal");
442                    }
443                    tok = in.nextToken();
444                    if (',' != tok) {
445                        in.pushBack();
446                    }
447                }
448                else if ("permission".equalsIgnoreCase(in.sval)) {
449                    if (STATE_PERMS != state) {
450                        error(url, in, "spurious 'permission'");
451                    }
452                    tok = in.nextToken();
453                    if (StreamTokenizer.TT_WORD != tok) {
454                        error(url, in, "expecting permission class name");
455                    }
456                    String className = in.sval;
457                    Class clazz = null;
458                    try {
459                        clazz = Class.forName(className);
460                    }
461                    catch(ClassNotFoundException cnfe) {
462                    }
463                    tok = in.nextToken();
464                    if (';' == tok) {
465                        if (null == clazz) {
466                            currentPerms.add(new UnresolvedPermission(className,
467                                null, null, (Certificate[])currentCerts.toArray(new Certificate[0])));
468                            continue;
469                        }
470                        try {
471                            currentPerms.add((Permission)clazz.newInstance());
472                        }
473                        catch(Exception x) {
474                            error(url, in, x.toString());
475                        }
476                        continue;
477                    }
478                    if ('"' != tok && '\'' != tok) {
479                        error(url, in, "expecting permission target");
480                    }
481                    String target = expand(in.sval);
482                    tok = in.nextToken();
483                    if (';' == tok) {
484                        if (null == clazz) {
485                            currentPerms.add(new UnresolvedPermission(className,
486                                target, null, (Certificate[])currentCerts.toArray(new Certificate[0])));
487                            continue;
488                        }
489                        try {
490                            Constructor c =
491                                clazz.getConstructor(new Class[]{String.class});
492                            currentPerms.add((Permission)c.newInstance(new Object[]{target}));
493                        }
494                        catch(Exception x) {
495                            error(url, in, x.toString());
496                        }
497                        continue;
498                    }
499                    if (',' != tok) {
500                        error(url, in, "expecting ','");
501                    }
502                    tok = in.nextToken();
503                    if (StreamTokenizer.TT_WORD == tok) {
504                        if (!"signedBy".equalsIgnoreCase(in.sval)) {
505                            error(url, in, "expecting 'signedBy'");
506                        }
507                        try {
508                            Constructor c =
509                                clazz.getConstructor(new Class[]{String.class});
510                            currentPerms.add((Permission)c.newInstance(new Object[]{target}));
511                        }
512                        catch(Exception x) {
513                            error(url, in, x.toString());
514                        }
515                        in.pushBack();
516                        continue;
517                    }
518                    if ('"' != tok && '\'' != tok) {
519                        error(url, in, "expecting permission action");
520                    }
521                    String action = in.sval;
522                    if (null == clazz) {
523                        currentPerms.add(new UnresolvedPermission(className,
524                            target, action, (Certificate[])currentCerts.toArray(new Certificate[0])));
525                        continue;
526                    }
527                    else {
528                        try {
529                            Constructor c = clazz.getConstructor(new Class[]{String.class, String.class});
530                            currentPerms.add((Permission)c.newInstance(new Object[]{target, action}));
531                        }
532                        catch(Exception x) {
533                            error(url, in, x.toString());
534                        }
535                    }
536                    tok = in.nextToken();
537                    if (';' != tok && ',' != tok) {
538                        error(url, in, "expecting ';' or ','");
539                    }
540                }
541            }
542        }
543    
544        /**
545         * Expand all instances of <code>"${property-name}"</code> into <code>System.getProperty("property-name")</code>.
546         */
547        private static String expand(String s) {
548            StringBuffer result = new StringBuffer();
549            StringBuffer prop = new StringBuffer();
550            int state = 0;
551            for(int i = 0; i < s.length(); i++) {
552                switch(state) {
553                    case 0:
554                        if ('$' == s.charAt(i)) {
555                            state = 1;
556                        }
557                        else {
558                            result.append(s.charAt(i));
559                        }
560                        break;
561                    case 1:
562                        if ('{' == s.charAt(i)) {
563                            state = 2;
564                        }
565                        else {
566                            state = 0;
567                            result.append('$').append(s.charAt(i));
568                        }
569                        break;
570                    case 2:
571                        if ('}' == s.charAt(i)) {
572                            String p = prop.toString();
573                            if ("/".equals(p)) {
574                                p = "file.separator";
575                            }
576                            p = System.getProperty(p);
577                            if (null == p) {
578                                p = "";
579                            }
580                            result.append(p);
581                            prop.setLength(0);
582                            state = 0;
583                        }
584                        else {
585                            prop.append(s.charAt(i));
586                        }
587                        break;
588                }
589            }
590            if (0 != state) {
591                result.append('$').append('{').append(prop);
592            }
593            return result.toString();
594        }
595    
596        /**
597         * I miss macros.
598         */
599        private static void error(URL base, StreamTokenizer in, String msg)
600            throws IOException {
601            throw new IOException(base + ":" + in.lineno() + ": " + msg);
602        }
603    }