001    package sysModel;
002    
003    import sysModel.fish.AFish;
004    
005    import javax.sound.sampled.AudioPermission;
006    import java.awt.*;
007    import java.io.FilePermission;
008    import java.io.SerializablePermission;
009    import java.lang.reflect.ReflectPermission;
010    import java.net.NetPermission;
011    import java.net.SocketPermission;
012    import java.security.Permission;
013    import java.security.SecurityPermission;
014    import java.sql.SQLPermission;
015    import java.util.PropertyPermission;
016    import java.util.Stack;
017    
018    /**
019     * Special security manager that does not permit fish to call System.exit() or System.setSecurityManager().
020     *
021     * @author Mathias Ricken
022     */
023    public class MBSSecurityManager extends SecurityManager {
024        /**
025         * Constructor.
026         * @param drJava true if DrJava is running
027         */
028        public MBSSecurityManager(boolean drJava) {
029            // TODO: Should create new security manager and load policy, but using the command line and DrJava is problematic right now
030            //_sm = new SecurityManager();
031            //PolicyFile pf = new PolicyFile();
032            //Policy.setPolicy(pf);
033            _isProtected = true;
034            _classPath = System.getProperty("java.class.path");
035            _pathSep = System.getProperty("path.separator");
036            _classPathDirs = _classPath.split(_pathSep);
037            _drJava = drJava;
038        }
039    
040        /**
041         * True if DrJava is present.
042         */
043        private boolean _drJava = false;
044    
045        /**
046         * Flag if actions are protected.
047         */
048        private boolean _isProtected = true;
049    
050        /**
051         * SecurityManager to delegate to.
052         */
053        private SecurityManager _sm = new SecurityManager();
054    
055        /**
056         * Class path.
057         */
058        private String _classPath;
059    
060        /**
061         * Path separator.
062         */
063        private String _pathSep;
064    
065        /**
066         * Class path directories.
067         */
068        private String[] _classPathDirs;
069    
070        /**
071         * Fish thread group.
072         */
073        private ThreadGroup _fishThreadGroup = new ThreadGroup("FishThreadGroup");
074    
075        /**
076         * Class loader.
077         */
078        private MBSClassLoader _classLoader = new MBSClassLoader(this);
079    
080        /**
081         * Stack of protection flags.
082         */
083        private Stack<Boolean> _protectionFlagStack = new Stack<Boolean>();
084    
085        /**
086         * Set the protection flag.
087         *
088         * @param isProtected true if actions are to be protected
089         */
090        public void setProtected(boolean isProtected) {
091            _isProtected = isProtected;
092        }
093    
094        /**
095         * Get the protection flag.
096         * @return true if actions are to be protected
097         */
098        public boolean isProtected() {
099            return _isProtected;
100        }
101    
102        /**
103         * Push the current protection flag onto the stack and set a new value.
104         * @param isProtected true if actions are to be protected
105         */
106        public void pushProtected(boolean isProtected) {
107            _protectionFlagStack.push(_isProtected);
108            _isProtected = isProtected;
109        }
110    
111        /**
112         * Pops the top protection flag from the stack and sets it as new value.
113         */
114        public void popProtected() {
115            _isProtected = _protectionFlagStack.pop();
116        }
117    
118        /**
119         * Returns true if the filename is on the class path.
120         *
121         * @param filename filename to test
122         * @return true if on classpath
123         */
124        protected boolean isOnClassPath(String filename) {
125            //System.out.println("Checking "+filename);
126            for (int i = 0; i < _classPathDirs.length; ++i) {
127                //System.out.println("\t"+_classPathDirs[i]);
128                if (filename.startsWith(_classPathDirs[i])) {
129                    return true;
130                }
131            }
132    
133            return false;
134        }
135    
136        /**
137         * Returns true if a fish is somewhere on the call stack.
138         *
139         * @return true if fish is a caller
140         */
141        protected boolean isFishCaller() {
142            Class[] classes = getClassContext();
143            for (int i = 0; i < classes.length; ++i) {
144                if (AFish.class.isAssignableFrom(classes[i])) {
145                    // System.out.println("Found a fish class: "+classes[0].getName());
146                    return true;
147                }
148            }
149            return false;
150        }
151    
152        /**
153         * Throws a <code>SecurityException</code> if the requested access, specified by the given permission, is not
154         * permitted based on the security policy currently in effect.
155         * <p/>
156         * This method calls <code>AccessController.checkPermission</code> with the given permission.
157         *
158         * @param perm the requested permission.
159         * @throws SecurityException    if access is not permitted based on the current security policy.
160         * @throws NullPointerException if the permission argument is <code>null</code>.
161         * @since 1.2
162         */
163        public void checkPermission(Permission perm) {
164            if (_drJava) {
165                return;
166            }
167    
168            boolean wasProtected = _isProtected;
169            try {
170                pushProtected(false);
171    
172                if (wasProtected) {
173                    if ((null != Thread.currentThread().getThreadGroup()) && (Thread.currentThread().getThreadGroup()==_fishThreadGroup) && (isFishCaller())) {
174                        // System.err.println("checkPermission: "+perm);
175                        if ((perm instanceof RuntimePermission)) {
176                            // disallow
177                            throw new SecurityException("Not allowed to deal with runtime!");
178                        }
179                        else if (perm instanceof AudioPermission) {
180                            // disallow
181                            throw new SecurityException("Not allowed to deal with audio!");
182                        }
183                        else if (perm instanceof AWTPermission) {
184                            // disallow
185                            throw new SecurityException("Not allowed to deal with AWT!");
186                        }
187                        else if ((perm instanceof FilePermission) &&
188                                ((!"read".equals(perm.getActions())) || ((!perm.getName().endsWith(".class") && (!perm.getName().endsWith(".jar"))))/*|| (!isOnClassPath(perm.getName()))*/)) {
189                            // TODO: isOnClassPath should be checked, but jar files and DrJava are problematic right now
190                            // disallow
191                            throw new SecurityException("Not allowed to deal with files!");
192                        }
193                        else if (perm instanceof NetPermission) {
194                            // disallow
195                            throw new SecurityException("Not allowed to deal with network!");
196                        }
197                        else if ((perm instanceof PropertyPermission) &&
198                                ((!"read".equals(perm.getActions())) || !"line.separator".equals(perm.getName()))) {
199                            // disallow
200                            StackTraceElement[] stes = Thread.currentThread().getStackTrace();
201                            for (StackTraceElement ste: stes) {
202                                System.out.println("### "+ste);
203                            }
204                            throw new SecurityException("Not allowed to deal with properties!");
205                        }
206                        else if (perm instanceof ReflectPermission) {
207                            // disallow
208                            throw new SecurityException("Not allowed to do reflection!");
209                        }
210                        else if (perm instanceof SecurityPermission) {
211                            // disallow
212                            throw new SecurityException("Not allowed to deal with security!");
213                        }
214                        else if (perm instanceof SerializablePermission) {
215                            // disallow
216                            throw new SecurityException("Not allowed to deal with serialization!");
217                        }
218                        else if (perm instanceof SocketPermission) {
219                            // disallow
220                            throw new SecurityException("Not allowed to deal with sockets!");
221                        }
222                        else if (perm instanceof SQLPermission) {
223                            // disallow
224                            throw new SecurityException("Not allowed to deal with SQL!");
225                        }
226                    }
227                }
228    
229                // delegate
230                // TODO: Should delegate, but using the command line and DrJava is problematic right now
231                //_sm.checkPermission(perm);
232            }
233            finally {
234               popProtected();
235            }
236        }
237    
238        /**
239         * Throws a <code>SecurityException</code> if the calling thread is not allowed to modify the thread argument.
240         * <p/>
241         * This method is invoked for the current security manager by the <code>stop</code>, <code>suspend</code>,
242         * <code>resume</code>, <code>setPriority</code>, <code>setName</code>, and <code>setDaemon</code> methods of class
243         * <code>Thread</code>.
244         * <p/>
245         * If the thread argument is a system thread (belongs to the thread group with a <code>null</code> parent) then this
246         * method calls <code>checkPermission</code> with the <code>RuntimePermission("modifyThread")</code> permission. If
247         * the thread argument is <i>not</i> a system thread, this method just returns silently.
248         * <p/>
249         * Applications that want a stricter policy should override this method. If this method is overridden, the method
250         * that overrides it should additionally check to see if the calling thread has the
251         * <code>RuntimePermission("modifyThread")</code> permission, and if so, return silently. This is to ensure that
252         * code granted that permission (such as the SDK itself) is allowed to manipulate any thread.
253         * <p/>
254         * If this method is overridden, then <code>super.checkAccess</code> should be called by the first statement in the
255         * overridden method, or the equivalent security check should be placed in the overridden method.
256         *
257         * @param t the thread to be checked.
258         * @throws SecurityException    if the calling thread does not have permission to modify the thread.
259         * @throws NullPointerException if the thread argument is <code>null</code>.
260         * @see Thread#resume() resume
261         * @see Thread#setDaemon(boolean) setDaemon
262         * @see Thread#setName(String) setName
263         * @see Thread#setPriority(int) setPriority
264         * @see Thread#stop() stop
265         * @see Thread#suspend() suspend
266         * @see #checkPermission(Permission) checkPermission
267         */
268        public void checkAccess(Thread t) {
269            if (_drJava) {
270                return;
271            }
272    
273            //System.err.println("checkAccess: Thread "+t.getId());
274    
275            super.checkAccess(t);
276    
277            if (_isProtected) {
278                checkPermission(new RuntimePermission("modifyThread"));
279            }
280        }
281    
282        /**
283         * Throws a <code>SecurityException</code> if the calling thread is not allowed to modify the thread group
284         * argument.
285         * <p/>
286         * This method is invoked for the current security manager when a new child thread or child thread group is created,
287         * and by the <code>setDaemon</code>, <code>setMaxPriority</code>, <code>stop</code>, <code>suspend</code>,
288         * <code>resume</code>, and <code>destroy</code> methods of class <code>ThreadGroup</code>.
289         * <p/>
290         * If the thread group argument is the system thread group ( has a <code>null</code> parent) then this method calls
291         * <code>checkPermission</code> with the <code>RuntimePermission("modifyThreadGroup")</code> permission. If the
292         * thread group argument is <i>not</i> the system thread group, this method just returns silently.
293         * <p/>
294         * Applications that want a stricter policy should override this method. If this method is overridden, the method
295         * that overrides it should additionally check to see if the calling thread has the
296         * <code>RuntimePermission("modifyThreadGroup")</code> permission, and if so, return silently. This is to ensure
297         * that code granted that permission (such as the SDK itself) is allowed to manipulate any thread.
298         * <p/>
299         * If this method is overridden, then <code>super.checkAccess</code> should be called by the first statement in the
300         * overridden method, or the equivalent security check should be placed in the overridden method.
301         *
302         * @param g the thread group to be checked.
303         * @throws SecurityException    if the calling thread does not have permission to modify the thread group.
304         * @throws NullPointerException if the thread group argument is <code>null</code>.
305         * @see ThreadGroup#destroy() destroy
306         * @see ThreadGroup#resume() resume
307         * @see ThreadGroup#setDaemon(boolean) setDaemon
308         * @see ThreadGroup#setMaxPriority(int) setMaxPriority
309         * @see ThreadGroup#stop() stop
310         * @see ThreadGroup#suspend() suspend
311         * @see #checkPermission(Permission) checkPermission
312         */
313        public void checkAccess(ThreadGroup g) {
314            if (_drJava) {
315                return;
316            }
317    
318            //System.err.println("checkAccess: ThreadGroup "+g.getName());
319    
320            super.checkAccess(g);
321    
322            if ((_isProtected) && (null != Thread.currentThread().getThreadGroup()) && (Thread.currentThread().getThreadGroup()==_fishThreadGroup)) {
323                if (!"system".equals(g.getName())) {
324                    throw new SecurityException("Not allowed to manipulate threads!");
325                }
326                checkPermission(new RuntimePermission("modifyThreadGroup"));
327            }
328        }
329    
330        /**
331         * Throws a <code>SecurityException</code> if the calling thread is not allowed to cause the Java Virtual Machine to
332         * halt with the specified status code.
333         * <p/>
334         * This method is invoked for the current security manager by the <code>exit</code> method of class
335         * <code>Runtime</code>. A status of <code>0</code> indicates success; other values indicate various errors.
336         * <p/>
337         * This method calls <code>checkPermission</code> with the <code>RuntimePermission("exitVM")</code> permission.
338         * <p/>
339         * If you override this method, then you should make a call to <code>super.checkExit</code> at the point the
340         * overridden method would normally throw an exception.
341         *
342         * @param status the exit status.
343         * @throws SecurityException if the calling thread does not have permission to halt the Java Virtual Machine with
344         *                           the specified status.
345         * @see Runtime#exit(int) exit
346         * @see #checkPermission(Permission) checkPermission
347         */
348        public void checkExit(int status) {
349            if (_drJava) {
350                return;
351            }
352    
353            if (_isProtected) {
354                throw new SecurityException("Not allowed to call System.exit()!");
355            }
356            else {
357                // delegate
358                // TODO: Should delegate, but using the command line and DrJava is problematic right now
359                //_sm.checkExit(status);
360            }
361        }
362    
363        /**
364         * Get the thread group the fish are run in.
365         * @return fish thread group
366         */
367        public ThreadGroup getFishThreadGroup() {
368            return _fishThreadGroup;
369        }
370    
371        /**
372         * Return the class loader.
373         * @return class loader
374         */
375        public ClassLoader getClassLoader() {
376            if (_drJava) {
377                return getClass().getClassLoader();
378            }
379            else {
380                return _classLoader;
381            }
382        }
383    }