001    /**
002     * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.security.pacl.checker;
016    
017    import com.liferay.portal.kernel.configuration.Filter;
018    import com.liferay.portal.kernel.deploy.DeployManagerUtil;
019    import com.liferay.portal.kernel.log.Log;
020    import com.liferay.portal.kernel.log.LogFactoryUtil;
021    import com.liferay.portal.kernel.servlet.ServletContextPool;
022    import com.liferay.portal.kernel.servlet.WebDirDetector;
023    import com.liferay.portal.kernel.servlet.taglib.FileAvailabilityUtil;
024    import com.liferay.portal.kernel.util.ContextPathUtil;
025    import com.liferay.portal.kernel.util.JavaConstants;
026    import com.liferay.portal.kernel.util.PathUtil;
027    import com.liferay.portal.kernel.util.PropsKeys;
028    import com.liferay.portal.kernel.util.ReleaseInfo;
029    import com.liferay.portal.kernel.util.ServerDetector;
030    import com.liferay.portal.kernel.util.StringPool;
031    import com.liferay.portal.kernel.util.StringUtil;
032    import com.liferay.portal.kernel.util.UniqueList;
033    import com.liferay.portal.kernel.util.Validator;
034    import com.liferay.portal.security.lang.PortalSecurityManagerThreadLocal;
035    import com.liferay.portal.security.pacl.PACLClassUtil;
036    import com.liferay.portal.servlet.DirectServletRegistryImpl;
037    import com.liferay.portal.util.PropsUtil;
038    import com.liferay.portal.util.PropsValues;
039    
040    import java.io.File;
041    import java.io.FilePermission;
042    import java.io.IOException;
043    
044    import java.net.URLClassLoader;
045    
046    import java.security.Permission;
047    
048    import java.util.Iterator;
049    import java.util.List;
050    import java.util.concurrent.CopyOnWriteArrayList;
051    
052    import javax.servlet.ServletContext;
053    
054    import sun.reflect.Reflection;
055    
056    /**
057     * @author Brian Wing Shun Chan
058     * @author Raymond Augé
059     */
060    public class FileChecker extends BaseChecker {
061    
062            public void afterPropertiesSet() {
063                    try {
064                            _rootDir = WebDirDetector.getRootDir(getClassLoader());
065                    }
066                    catch (Exception e) {
067    
068                            // This means the WAR is probably not exploded
069    
070                    }
071    
072                    if (_log.isDebugEnabled()) {
073                            _log.debug("Root directory " + _rootDir);
074                    }
075    
076                    ServletContext servletContext = ServletContextPool.get(
077                            getServletContextName());
078    
079                    if (servletContext != null) {
080                            File tempDir = (File)servletContext.getAttribute(
081                                    JavaConstants.JAVAX_SERVLET_CONTEXT_TEMPDIR);
082    
083                            _workDir = tempDir.getAbsolutePath();
084    
085                            if (_log.isDebugEnabled()) {
086                                    _log.debug("Work directory " + _workDir);
087                            }
088                    }
089    
090                    _defaultReadPathsFromArray = new String[] {
091                            "${auto.deploy.installed.dir}",
092                            "${catalina.base}",
093                            "${com.sun.aas.instanceRoot}",
094                            "${com.sun.aas.installRoot}",
095                            "${file.separator}",
096                            "${java.io.tmpdir}",
097                            "${jboss.home.dir}",
098                            "${jetty.home}",
099                            "${jonas.base}",
100                            "${liferay.web.portal.dir}",
101                            "${line.separator}",
102                            "${org.apache.geronimo.home.dir}",
103                            "${path.separator}",
104                            "${plugin.servlet.context.name}",
105                            "${release.info.version}",
106                            "${resin.home}",
107                            "${user.dir}",
108                            "${user.home}",
109                            "${user.name}",
110                            "${weblogic.domain.dir}",
111                            "${websphere.profile.dir}",
112                            StringPool.DOUBLE_SLASH
113                    };
114    
115                    String installedDir = StringPool.BLANK;
116    
117                    try {
118                            if (DeployManagerUtil.getDeployManager() != null) {
119                                    installedDir = DeployManagerUtil.getInstalledDir();
120                            }
121                    }
122                    catch (Exception e) {
123                            _log.error(e, e);
124                    }
125    
126                    _defaultReadPathsToArray = new String[] {
127                            installedDir, System.getProperty("catalina.base"),
128                            System.getProperty("com.sun.aas.instanceRoot"),
129                            System.getProperty("com.sun.aas.installRoot"),
130                            System.getProperty("file.separator"),
131                            System.getProperty("java.io.tmpdir"),
132                            System.getProperty("jboss.home.dir"),
133                            System.getProperty("jetty.home"), System.getProperty("jonas.base"),
134                            _portalDir, System.getProperty("line.separator"),
135                            System.getProperty("org.apache.geronimo.home.dir"),
136                            System.getProperty("path.separator"), getServletContextName(),
137                            ReleaseInfo.getVersion(), System.getProperty("resin.home"),
138                            System.getProperty("user.dir"), System.getProperty("user.home"),
139                            System.getProperty("user.name"), System.getenv("DOMAIN_HOME"),
140                            System.getenv("USER_INSTALL_ROOT"), StringPool.SLASH
141                    };
142    
143                    if (_log.isDebugEnabled()) {
144                            _log.debug(
145                                    "Default read paths replace with " +
146                                            StringUtil.merge(_defaultReadPathsToArray));
147                    }
148    
149                    initPermissions();
150            }
151    
152            public void checkPermission(Permission permission) {
153                    String name = permission.getName();
154                    String actions = permission.getActions();
155    
156                    if (actions.equals(FILE_PERMISSION_ACTION_DELETE)) {
157                            if (!hasDelete(permission)) {
158                                    throwSecurityException(
159                                            _log, "Attempted to delete file " + name);
160                            }
161                    }
162                    else if (actions.equals(FILE_PERMISSION_ACTION_EXECUTE)) {
163                            if (!hasExecute(permission)) {
164                                    throwSecurityException(
165                                            _log, "Attempted to execute file " + name);
166                            }
167                    }
168                    else if (actions.equals(FILE_PERMISSION_ACTION_READ)) {
169                            if (PortalSecurityManagerThreadLocal.isCheckReadFile() &&
170                                    !hasRead(permission)) {
171    
172                                    throwSecurityException(_log, "Attempted to read file " + name);
173                            }
174                    }
175                    else if (actions.equals(FILE_PERMISSION_ACTION_WRITE)) {
176                            if (!hasWrite(permission)) {
177                                    throwSecurityException(_log, "Attempted to write file " + name);
178                            }
179                    }
180            }
181    
182            protected void addCanonicalPath(List<String> paths, String path) {
183                    Iterator<String> itr = paths.iterator();
184    
185                    while (itr.hasNext()) {
186                            String curPath = itr.next();
187    
188                            if (curPath.startsWith(path) &&
189                                    (curPath.length() > path.length())) {
190    
191                                    itr.remove();
192                            }
193                            else if (path.startsWith(curPath)) {
194                                    return;
195                            }
196                    }
197    
198                    path = StringUtil.replace(
199                            path, StringPool.BACK_SLASH, StringPool.SLASH);
200    
201                    if (path.endsWith(StringPool.SLASH)) {
202                            path = path + "-";
203                    }
204    
205                    paths.add(path);
206            }
207    
208            protected void addCanonicalPaths(List<String> paths, File directory)
209                    throws IOException {
210    
211                    addCanonicalPath(
212                            paths, directory.getCanonicalPath() + StringPool.SLASH);
213    
214                    for (File file : directory.listFiles()) {
215                            if (file.isDirectory()) {
216                                    addCanonicalPaths(paths, file);
217                            }
218                            else {
219                                    File canonicalFile = new File(file.getCanonicalPath());
220    
221                                    File parentFile = canonicalFile.getParentFile();
222    
223                                    addCanonicalPath(
224                                            paths, parentFile.getPath() + StringPool.SLASH);
225                            }
226                    }
227            }
228    
229            protected void addDefaultReadPaths(List<String> paths, String selector) {
230                    String[] pathsArray = PropsUtil.getArray(
231                            PropsKeys.PORTAL_SECURITY_MANAGER_FILE_CHECKER_DEFAULT_READ_PATHS,
232                            new Filter(selector));
233    
234                    for (String path : pathsArray) {
235                            path = StringUtil.replace(
236                                    path, _defaultReadPathsFromArray, _defaultReadPathsToArray);
237    
238                            paths.add(path);
239                    }
240            }
241    
242            protected void addPermission(
243                    List<Permission> permissions, String path, String actions) {
244    
245                    if (_log.isDebugEnabled()) {
246                            _log.debug("Allowing " + actions + " on " + path);
247                    }
248    
249                    String unixPath = PathUtil.toUnixPath(path);
250    
251                    Permission unixPermission = new FilePermission(unixPath, actions);
252    
253                    permissions.add(unixPermission);
254    
255                    String windowsPath = PathUtil.toWindowsPath(path);
256    
257                    Permission windowsPermission = new FilePermission(windowsPath, actions);
258    
259                    permissions.add(windowsPermission);
260            }
261    
262            protected List<Permission> getPermissions(String key, String actions) {
263                    List<Permission> permissions = new CopyOnWriteArrayList<Permission>();
264    
265                    String value = getProperty(key);
266    
267                    if (value != null) {
268                            value = StringUtil.replace(
269                                    value, _defaultReadPathsFromArray, _defaultReadPathsToArray);
270    
271                            String[] paths = StringUtil.split(value);
272    
273                            if (value.contains("${comma}")) {
274                                    for (int i = 0; i < paths.length; i++) {
275                                            paths[i] = StringUtil.replace(
276                                                    paths[i], "${comma}", StringPool.COMMA);
277                                    }
278                            }
279    
280                            for (String path : paths) {
281                                    addPermission(permissions, path, actions);
282                            }
283                    }
284    
285                    // Plugin can do anything, except execute, in its own work folder
286    
287                    String pathContext = ContextPathUtil.getContextPath(
288                            PropsValues.PORTAL_CTX);
289    
290                    ServletContext servletContext = ServletContextPool.get(pathContext);
291    
292                    if (!actions.equals(FILE_PERMISSION_ACTION_EXECUTE) &&
293                            (_workDir != null)) {
294    
295                            addPermission(permissions, _workDir, actions);
296                            addPermission(permissions, _workDir + "/-", actions);
297    
298                            if (servletContext != null) {
299                                    File tempDir = (File)servletContext.getAttribute(
300                                            JavaConstants.JAVAX_SERVLET_CONTEXT_TEMPDIR);
301    
302                                    String tempDirAbsolutePath = tempDir.getAbsolutePath();
303    
304                                    if (_log.isDebugEnabled()) {
305                                            _log.debug("Temp directory " + tempDirAbsolutePath);
306                                    }
307    
308                                    if (actions.equals(FILE_PERMISSION_ACTION_READ)) {
309                                            addPermission(permissions, tempDirAbsolutePath, actions);
310                                    }
311    
312                                    addPermission(permissions, tempDirAbsolutePath + "/-", actions);
313                            }
314                    }
315    
316                    if (!actions.equals(FILE_PERMISSION_ACTION_READ)) {
317                            return permissions;
318                    }
319    
320                    List<String> paths = new UniqueList<String>();
321    
322                    // JDK
323    
324                    // There may be JARs in the system library that are symlinked. We must
325                    // include their canonical paths or they will fail permission checks.
326    
327                    try {
328                            File file = new File(System.getProperty("java.home") + "/lib");
329    
330                            addCanonicalPaths(paths, file);
331                    }
332                    catch (IOException ioe) {
333                            _log.error(ioe, ioe);
334                    }
335    
336                    // Shared libs
337    
338                    if (Validator.isNotNull(_globalSharedLibDir)) {
339                            paths.add(_globalSharedLibDir + "-");
340                    }
341    
342                    // Plugin
343    
344                    if (_rootDir != null) {
345                            paths.add(_rootDir + "-");
346                    }
347    
348                    // Portal
349    
350                    addDefaultReadPaths(paths, ServerDetector.getServerId());
351    
352                    for (String path : paths) {
353                            addPermission(permissions, path, actions);
354                    }
355    
356                    return permissions;
357            }
358    
359            protected boolean hasDelete(Permission permission) {
360                    for (Permission deleteFilePermission : _deletePermissions) {
361                            if (deleteFilePermission.implies(permission)) {
362                                    return true;
363                            }
364                    }
365    
366                    if (ServerDetector.isResin()) {
367                            for (int i = 7;; i++) {
368                                    Class<?> callerClass = Reflection.getCallerClass(i);
369    
370                                    if (callerClass == null) {
371                                            return false;
372                                    }
373    
374                                    String callerClassName = callerClass.getName();
375    
376                                    if (callerClassName.equals(_CLASS_NAME_FILE_PATH)) {
377                                            String actualClassLocation = PACLClassUtil.getClassLocation(
378                                                    callerClass);
379                                            String expectedClassLocation = PathUtil.toUnixPath(
380                                                    System.getProperty("resin.home") + "/lib/resin.jar!/");
381    
382                                            return actualClassLocation.contains(expectedClassLocation);
383                                    }
384                            }
385                    }
386    
387                    return false;
388            }
389    
390            protected boolean hasExecute(Permission permission) {
391                    for (Permission executeFilePermission : _executePermissions) {
392                            if (executeFilePermission.implies(permission)) {
393                                    return true;
394                            }
395                    }
396    
397                    return false;
398            }
399    
400            protected boolean hasRead(Permission permission) {
401                    for (Permission readFilePermission : _readPermissions) {
402                            if (readFilePermission.implies(permission)) {
403                                    return true;
404                            }
405                    }
406    
407                    if (isJSPCompiler(permission.getName(), FILE_PERMISSION_ACTION_READ)) {
408                            return true;
409                    }
410    
411                    for (int i = 7;; i++) {
412                            Class<?> callerClass = Reflection.getCallerClass(i);
413    
414                            if (callerClass == null) {
415                                    return false;
416                            }
417    
418                            if ((callerClass == DirectServletRegistryImpl.class) ||
419                                    (callerClass == FileAvailabilityUtil.class)) {
420    
421                                    return true;
422                            }
423    
424                            if (ClassLoader.class.isAssignableFrom(callerClass)) {
425                                    String callerClassName = callerClass.getName();
426    
427                                    if (!callerClassName.equals(_CLASS_NAME_METHOD_UTIL)) {
428                                            return true;
429                                    }
430                            }
431    
432                            if (ServerDetector.isGlassfish()) {
433                                    Class<?> enclosingClass = callerClass.getEnclosingClass();
434    
435                                    if (enclosingClass != null) {
436                                            if ((enclosingClass.getEnclosingClass() ==
437                                                            URLClassLoader.class) &&
438                                                    CheckerUtil.isAccessControllerDoPrivileged(i + 1)) {
439    
440                                                    return true;
441                                            }
442                                    }
443                            }
444                            else if (ServerDetector.isResin()) {
445                                    String callerClassName = callerClass.getName();
446    
447                                    if (callerClassName.equals(_CLASS_NAME_FILE_PATH)) {
448                                            String actualClassLocation = PACLClassUtil.getClassLocation(
449                                                    callerClass);
450                                            String expectedClassLocation = PathUtil.toUnixPath(
451                                                    System.getProperty("resin.home") + "/lib/resin.jar!/");
452    
453                                            return actualClassLocation.contains(expectedClassLocation);
454                                    }
455                            }
456                    }
457            }
458    
459            protected boolean hasWrite(Permission permission) {
460                    for (Permission writeFilePermission : _writePermissions) {
461                            if (writeFilePermission.implies(permission)) {
462                                    return true;
463                            }
464                    }
465    
466                    if (ServerDetector.isResin()) {
467                            for (int i = 7;; i++) {
468                                    Class<?> callerClass = Reflection.getCallerClass(i);
469    
470                                    if (callerClass == null) {
471                                            return false;
472                                    }
473    
474                                    String callerClassName = callerClass.getName();
475    
476                                    if (callerClassName.equals(_CLASS_NAME_FILE_PATH)) {
477                                            String actualClassLocation = PACLClassUtil.getClassLocation(
478                                                    callerClass);
479                                            String expectedClassLocation = PathUtil.toUnixPath(
480                                                    System.getProperty("resin.home") + "/lib/resin.jar!/");
481    
482                                            return actualClassLocation.contains(expectedClassLocation);
483                                    }
484                            }
485                    }
486                    else if (ServerDetector.isWebSphere()) {
487                            if (isJSPCompiler(
488                                            permission.getName(), FILE_PERMISSION_ACTION_WRITE)) {
489    
490                                    return true;
491                            }
492                    }
493    
494                    return false;
495            }
496    
497            protected void initPermissions() {
498                    _deletePermissions = getPermissions(
499                            "security-manager-files-delete", FILE_PERMISSION_ACTION_DELETE);
500                    _executePermissions = getPermissions(
501                            "security-manager-files-execute", FILE_PERMISSION_ACTION_EXECUTE);
502                    _readPermissions = getPermissions(
503                            "security-manager-files-read", FILE_PERMISSION_ACTION_READ);
504                    _writePermissions = getPermissions(
505                            "security-manager-files-write", FILE_PERMISSION_ACTION_WRITE);
506            }
507    
508            private static final String _CLASS_NAME_FILE_PATH =
509                    "com.caucho.vfs.FilePath";
510    
511            private static final String _CLASS_NAME_METHOD_UTIL =
512                    "sun.reflect.misc.MethodUtil";
513    
514            private static Log _log = LogFactoryUtil.getLog(FileChecker.class);
515    
516            private String[] _defaultReadPathsFromArray;
517            private String[] _defaultReadPathsToArray;
518            private List<Permission> _deletePermissions;
519            private List<Permission> _executePermissions;
520            private String _globalSharedLibDir =
521                    PropsValues.LIFERAY_LIB_GLOBAL_SHARED_DIR;
522            private String _portalDir = PropsValues.LIFERAY_WEB_PORTAL_DIR;
523            private List<Permission> _readPermissions;
524            private String _rootDir;
525            private String _workDir;
526            private List<Permission> _writePermissions;
527    
528    }