001    /**
002     * Copyright (c) 2000-2013 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.util.ContextPathUtil;
024    import com.liferay.portal.kernel.util.JavaConstants;
025    import com.liferay.portal.kernel.util.PathUtil;
026    import com.liferay.portal.kernel.util.PropsKeys;
027    import com.liferay.portal.kernel.util.ReleaseInfo;
028    import com.liferay.portal.kernel.util.ServerDetector;
029    import com.liferay.portal.kernel.util.StringPool;
030    import com.liferay.portal.kernel.util.StringUtil;
031    import com.liferay.portal.kernel.util.UniqueList;
032    import com.liferay.portal.kernel.util.Validator;
033    import com.liferay.portal.util.PropsUtil;
034    import com.liferay.portal.util.PropsValues;
035    
036    import java.io.File;
037    import java.io.FilePermission;
038    import java.io.IOException;
039    
040    import java.net.JarURLConnection;
041    import java.net.URL;
042    import java.net.URLConnection;
043    
044    import java.security.Permission;
045    import java.security.Permissions;
046    
047    import java.util.Enumeration;
048    import java.util.Iterator;
049    import java.util.List;
050    
051    import javax.servlet.ServletContext;
052    
053    import sun.reflect.Reflection;
054    
055    /**
056     * @author Brian Wing Shun Chan
057     * @author Raymond Augé
058     */
059    public class FileChecker extends BaseChecker {
060    
061            public void afterPropertiesSet() {
062                    try {
063                            _rootDir = WebDirDetector.getRootDir(getClassLoader());
064                    }
065                    catch (Exception e) {
066    
067                            // This means the WAR is probably not exploded
068    
069                    }
070    
071                    if (_log.isDebugEnabled()) {
072                            _log.debug("Root directory " + _rootDir);
073                    }
074    
075                    ServletContext servletContext = ServletContextPool.get(
076                            getServletContextName());
077    
078                    if (servletContext != null) {
079                            File tempDir = (File)servletContext.getAttribute(
080                                    JavaConstants.JAVAX_SERVLET_CONTEXT_TEMPDIR);
081    
082                            _workDir = tempDir.getAbsolutePath();
083    
084                            if (_log.isDebugEnabled()) {
085                                    _log.debug("Work directory " + _workDir);
086                            }
087                    }
088    
089                    _defaultReadPathsFromArray = new String[] {
090                            "${/}",
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                            "${liferay.home}",
102                            "${line.separator}",
103                            "${org.apache.geronimo.home.dir}",
104                            "${path.separator}",
105                            "${plugin.servlet.context.name}",
106                            "${release.info.version}",
107                            "${resin.home}",
108                            "${user.dir}",
109                            "${user.home}",
110                            "${user.name}",
111                            "${weblogic.domain.dir}",
112                            "${websphere.profile.dir}",
113                            StringPool.DOUBLE_SLASH
114                    };
115    
116                    String installedDir = StringPool.BLANK;
117    
118                    try {
119                            if (DeployManagerUtil.getDeployManager() != null) {
120                                    installedDir = DeployManagerUtil.getInstalledDir();
121                            }
122                    }
123                    catch (Exception e) {
124                            _log.error(e, e);
125                    }
126    
127                    _defaultReadPathsToArray = new String[] {
128                            System.getProperty("file.separator"), installedDir,
129                            System.getProperty("catalina.base"),
130                            System.getProperty("com.sun.aas.instanceRoot"),
131                            System.getProperty("com.sun.aas.installRoot"),
132                            System.getProperty("file.separator"),
133                            System.getProperty("java.io.tmpdir"),
134                            System.getProperty("jboss.home.dir"),
135                            System.getProperty("jetty.home"), System.getProperty("jonas.base"),
136                            _portalDir, PropsValues.LIFERAY_HOME,
137                            System.getProperty("line.separator"),
138                            System.getProperty("org.apache.geronimo.home.dir"),
139                            System.getProperty("path.separator"), getServletContextName(),
140                            ReleaseInfo.getVersion(), System.getProperty("resin.home"),
141                            System.getProperty("user.dir"), System.getProperty("user.home"),
142                            System.getProperty("user.name"), System.getenv("DOMAIN_HOME"),
143                            System.getenv("USER_INSTALL_ROOT"), StringPool.SLASH
144                    };
145    
146                    if (_log.isDebugEnabled()) {
147                            _log.debug(
148                                    "Default read paths replace with " +
149                                            StringUtil.merge(_defaultReadPathsToArray));
150                    }
151    
152                    initPermissions();
153            }
154    
155            @Override
156            public AuthorizationProperty generateAuthorizationProperty(
157                    Object... arguments) {
158    
159                    if ((arguments == null) || (arguments.length != 1) ||
160                            !(arguments[0] instanceof Permission)) {
161    
162                            return null;
163                    }
164    
165                    Permission permission = (Permission)arguments[0];
166    
167                    String actions = permission.getActions();
168    
169                    String key = null;
170    
171                    if (actions.equals(FILE_PERMISSION_ACTION_DELETE)) {
172                            key = "security-manager-files-delete";
173                    }
174                    else if (actions.equals(FILE_PERMISSION_ACTION_EXECUTE)) {
175                            key = "security-manager-files-execute";
176                    }
177                    else if (actions.equals(FILE_PERMISSION_ACTION_READ)) {
178                            key = "security-manager-files-read";
179                    }
180                    else if (actions.equals(FILE_PERMISSION_ACTION_WRITE)) {
181                            key = "security-manager-files-write";
182                    }
183                    else {
184                            return null;
185                    }
186    
187                    AuthorizationProperty authorizationProperty =
188                            new AuthorizationProperty();
189    
190                    authorizationProperty.setKey(key);
191                    authorizationProperty.setValue(permission.getName());
192    
193                    return authorizationProperty;
194            }
195    
196            public String getRootDir() {
197                    return _rootDir;
198            }
199    
200            public boolean implies(Permission permission) {
201                    if (_permissions.implies(permission)) {
202                            return true;
203                    }
204    
205                    int stackIndex = getStackIndex(10, 9);
206    
207                    Class<?> callerClass1 = Reflection.getCallerClass(stackIndex);
208                    Class<?> callerClass2 = Reflection.getCallerClass(stackIndex + 1);
209    
210                    Package callerClass1Package = callerClass1.getPackage();
211    
212                    String callerClass1PackageName = callerClass1Package.getName();
213    
214                    if (callerClass1PackageName.startsWith("java.") &&
215                            !callerClass1.equals(ProcessBuilder.class) &&
216                            isTrustedCaller(callerClass2, permission)) {
217    
218                            return true;
219                    }
220    
221                    logSecurityException(
222                            _log,
223                            "Attempted to " + permission.getActions() + " on file " +
224                                    permission.getName());
225    
226                    return false;
227            }
228    
229            protected void addCanonicalPath(List<String> paths, String path) {
230                    Iterator<String> itr = paths.iterator();
231    
232                    while (itr.hasNext()) {
233                            String curPath = itr.next();
234    
235                            if (curPath.startsWith(path) &&
236                                    (curPath.length() > path.length())) {
237    
238                                    itr.remove();
239                            }
240                            else if (path.startsWith(curPath)) {
241                                    return;
242                            }
243                    }
244    
245                    path = StringUtil.replace(
246                            path, StringPool.BACK_SLASH, StringPool.SLASH);
247    
248                    if (path.endsWith(StringPool.SLASH)) {
249                            path = path + "-";
250                    }
251    
252                    paths.add(path);
253            }
254    
255            protected void addCanonicalPaths(List<String> paths, File directory)
256                    throws IOException {
257    
258                    addCanonicalPath(
259                            paths, directory.getCanonicalPath() + StringPool.SLASH);
260    
261                    File[] files = directory.listFiles();
262    
263                    if ((files == null) || (files.length == 0)) {
264                            return;
265                    }
266    
267                    for (File file : files) {
268                            if (file.isDirectory()) {
269                                    addCanonicalPaths(paths, file);
270                            }
271                            else {
272                                    File canonicalFile = new File(file.getCanonicalPath());
273    
274                                    File parentFile = canonicalFile.getParentFile();
275    
276                                    addCanonicalPath(
277                                            paths, parentFile.getPath() + StringPool.SLASH);
278                            }
279                    }
280            }
281    
282            protected void addDefaultReadPaths(List<String> paths, String selector) {
283                    String[] pathsArray = PropsUtil.getArray(
284                            PropsKeys.PORTAL_SECURITY_MANAGER_FILE_CHECKER_DEFAULT_READ_PATHS,
285                            new Filter(selector));
286    
287                    for (String path : pathsArray) {
288                            path = StringUtil.replace(
289                                    path, _defaultReadPathsFromArray, _defaultReadPathsToArray);
290    
291                            paths.add(path);
292                    }
293            }
294    
295            protected void addPermission(String path, String actions) {
296                    if (_log.isDebugEnabled()) {
297                            _log.debug("Allowing " + actions + " on " + path);
298                    }
299    
300                    String unixPath = PathUtil.toUnixPath(path);
301    
302                    Permission unixPermission = new FilePermission(unixPath, actions);
303    
304                    _permissions.add(unixPermission);
305    
306                    String windowsPath = PathUtil.toWindowsPath(path);
307    
308                    Permission windowsPermission = new FilePermission(windowsPath, actions);
309    
310                    _permissions.add(windowsPermission);
311            }
312    
313            protected void getPermissions(String key, String actions) {
314                    String value = getProperty(key);
315    
316                    if (value != null) {
317                            value = StringUtil.replace(
318                                    value, _defaultReadPathsFromArray, _defaultReadPathsToArray);
319    
320                            String[] paths = StringUtil.split(value);
321    
322                            if (value.contains("${comma}")) {
323                                    for (int i = 0; i < paths.length; i++) {
324                                            paths[i] = StringUtil.replace(
325                                                    paths[i], "${comma}", StringPool.COMMA);
326                                    }
327                            }
328    
329                            for (String path : paths) {
330                                    addPermission(path, actions);
331                            }
332                    }
333    
334                    // Plugin can do anything, except execute, in its own work folder
335    
336                    String pathContext = ContextPathUtil.getContextPath(
337                            PropsValues.PORTAL_CTX);
338    
339                    ServletContext servletContext = ServletContextPool.get(pathContext);
340    
341                    if (!actions.equals(FILE_PERMISSION_ACTION_EXECUTE) &&
342                            (_workDir != null)) {
343    
344                            addPermission(_workDir, actions);
345                            addPermission(_workDir + "/-", actions);
346    
347                            if (servletContext != null) {
348                                    File tempDir = (File)servletContext.getAttribute(
349                                            JavaConstants.JAVAX_SERVLET_CONTEXT_TEMPDIR);
350    
351                                    String tempDirAbsolutePath = tempDir.getAbsolutePath();
352    
353                                    if (_log.isDebugEnabled()) {
354                                            _log.debug("Temp directory " + tempDirAbsolutePath);
355                                    }
356    
357                                    if (actions.equals(FILE_PERMISSION_ACTION_READ)) {
358                                            addPermission(tempDirAbsolutePath, actions);
359                                    }
360    
361                                    addPermission(tempDirAbsolutePath + "/-", actions);
362                            }
363                    }
364    
365                    if (!actions.equals(FILE_PERMISSION_ACTION_READ)) {
366                            return;
367                    }
368    
369                    List<String> paths = new UniqueList<String>();
370    
371                    // JDK
372    
373                    // There may be JARs in the system library that are symlinked. We must
374                    // include their canonical paths or they will fail permission checks.
375    
376                    try {
377                            File file = new File(System.getProperty("java.home") + "/lib");
378    
379                            addCanonicalPaths(paths, file);
380    
381                            ClassLoader classLoader = ClassLoader.getSystemClassLoader();
382    
383                            Enumeration<URL> enumeration = classLoader.getResources(
384                                    "META-INF/MANIFEST.MF");
385    
386                            while (enumeration.hasMoreElements()) {
387                                    URL url = enumeration.nextElement();
388    
389                                    URLConnection urlConnection = url.openConnection();
390    
391                                    if (urlConnection instanceof JarURLConnection) {
392                                            JarURLConnection jarURLConnection =
393                                                    (JarURLConnection)url.openConnection();
394    
395                                            URL jarFileURL = jarURLConnection.getJarFileURL();
396    
397                                            String fileName = jarFileURL.getFile();
398    
399                                            int pos = fileName.lastIndexOf(File.separatorChar);
400    
401                                            if (pos != -1) {
402                                                    fileName = fileName.substring(0, pos + 1);
403                                            }
404    
405                                            addCanonicalPath(paths, fileName);
406                                    }
407                            }
408                    }
409                    catch (IOException ioe) {
410                            _log.error(ioe, ioe);
411                    }
412    
413                    // Shared libs
414    
415                    if (Validator.isNotNull(_globalSharedLibDir)) {
416                            paths.add(_globalSharedLibDir + "-");
417                    }
418    
419                    // Plugin
420    
421                    if (_rootDir != null) {
422                            paths.add(_rootDir);
423                            paths.add(_rootDir + "-");
424                    }
425    
426                    // Portal
427    
428                    addDefaultReadPaths(paths, ServerDetector.getServerId());
429    
430                    for (String path : paths) {
431                            addPermission(path, actions);
432                    }
433            }
434    
435            protected void initPermissions() {
436                    getPermissions(
437                            "security-manager-files-delete", FILE_PERMISSION_ACTION_DELETE);
438                    getPermissions(
439                            "security-manager-files-execute", FILE_PERMISSION_ACTION_EXECUTE);
440                    getPermissions(
441                            "security-manager-files-read", FILE_PERMISSION_ACTION_READ);
442                    getPermissions(
443                            "security-manager-files-write", FILE_PERMISSION_ACTION_WRITE);
444            }
445    
446            private static Log _log = LogFactoryUtil.getLog(FileChecker.class);
447    
448            private String[] _defaultReadPathsFromArray;
449            private String[] _defaultReadPathsToArray;
450            private String _globalSharedLibDir =
451                    PropsValues.LIFERAY_LIB_GLOBAL_SHARED_DIR;
452            private Permissions _permissions = new Permissions();
453            private String _portalDir = PropsValues.LIFERAY_WEB_PORTAL_DIR;
454            private String _rootDir;
455            private String _workDir;
456    
457    }