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            @Override
183            public AuthorizationProperty generateAuthorizationProperty(
184                    Object... arguments) {
185    
186                    if ((arguments == null) || (arguments.length != 1) ||
187                            !(arguments[0] instanceof Permission)) {
188    
189                            return null;
190                    }
191    
192                    Permission permission = (Permission)arguments[0];
193    
194                    String actions = permission.getActions();
195    
196                    String key = null;
197    
198                    if (actions.equals(FILE_PERMISSION_ACTION_DELETE)) {
199                            key = "security-manager-files-delete";
200                    }
201                    else if (actions.equals(FILE_PERMISSION_ACTION_EXECUTE)) {
202                            key = "security-manager-files-execute";
203                    }
204                    else if (actions.equals(FILE_PERMISSION_ACTION_READ)) {
205                            key = "security-manager-files-read";
206                    }
207                    else if (actions.equals(FILE_PERMISSION_ACTION_WRITE)) {
208                            key = "security-manager-files-write";
209                    }
210                    else {
211                            return null;
212                    }
213    
214                    AuthorizationProperty authorizationProperty =
215                            new AuthorizationProperty();
216    
217                    authorizationProperty.setKey(key);
218                    authorizationProperty.setValue(permission.getName());
219    
220                    return authorizationProperty;
221            }
222    
223            protected void addCanonicalPath(List<String> paths, String path) {
224                    Iterator<String> itr = paths.iterator();
225    
226                    while (itr.hasNext()) {
227                            String curPath = itr.next();
228    
229                            if (curPath.startsWith(path) &&
230                                    (curPath.length() > path.length())) {
231    
232                                    itr.remove();
233                            }
234                            else if (path.startsWith(curPath)) {
235                                    return;
236                            }
237                    }
238    
239                    path = StringUtil.replace(
240                            path, StringPool.BACK_SLASH, StringPool.SLASH);
241    
242                    if (path.endsWith(StringPool.SLASH)) {
243                            path = path + "-";
244                    }
245    
246                    paths.add(path);
247            }
248    
249            protected void addCanonicalPaths(List<String> paths, File directory)
250                    throws IOException {
251    
252                    addCanonicalPath(
253                            paths, directory.getCanonicalPath() + StringPool.SLASH);
254    
255                    for (File file : directory.listFiles()) {
256                            if (file.isDirectory()) {
257                                    addCanonicalPaths(paths, file);
258                            }
259                            else {
260                                    File canonicalFile = new File(file.getCanonicalPath());
261    
262                                    File parentFile = canonicalFile.getParentFile();
263    
264                                    addCanonicalPath(
265                                            paths, parentFile.getPath() + StringPool.SLASH);
266                            }
267                    }
268            }
269    
270            protected void addDefaultReadPaths(List<String> paths, String selector) {
271                    String[] pathsArray = PropsUtil.getArray(
272                            PropsKeys.PORTAL_SECURITY_MANAGER_FILE_CHECKER_DEFAULT_READ_PATHS,
273                            new Filter(selector));
274    
275                    for (String path : pathsArray) {
276                            path = StringUtil.replace(
277                                    path, _defaultReadPathsFromArray, _defaultReadPathsToArray);
278    
279                            paths.add(path);
280                    }
281            }
282    
283            protected void addPermission(
284                    List<Permission> permissions, String path, String actions) {
285    
286                    if (_log.isDebugEnabled()) {
287                            _log.debug("Allowing " + actions + " on " + path);
288                    }
289    
290                    String unixPath = PathUtil.toUnixPath(path);
291    
292                    Permission unixPermission = new FilePermission(unixPath, actions);
293    
294                    permissions.add(unixPermission);
295    
296                    String windowsPath = PathUtil.toWindowsPath(path);
297    
298                    Permission windowsPermission = new FilePermission(windowsPath, actions);
299    
300                    permissions.add(windowsPermission);
301            }
302    
303            protected List<Permission> getPermissions(String key, String actions) {
304                    List<Permission> permissions = new CopyOnWriteArrayList<Permission>();
305    
306                    String value = getProperty(key);
307    
308                    if (value != null) {
309                            value = StringUtil.replace(
310                                    value, _defaultReadPathsFromArray, _defaultReadPathsToArray);
311    
312                            String[] paths = StringUtil.split(value);
313    
314                            if (value.contains("${comma}")) {
315                                    for (int i = 0; i < paths.length; i++) {
316                                            paths[i] = StringUtil.replace(
317                                                    paths[i], "${comma}", StringPool.COMMA);
318                                    }
319                            }
320    
321                            for (String path : paths) {
322                                    addPermission(permissions, path, actions);
323                            }
324                    }
325    
326                    // Plugin can do anything, except execute, in its own work folder
327    
328                    String pathContext = ContextPathUtil.getContextPath(
329                            PropsValues.PORTAL_CTX);
330    
331                    ServletContext servletContext = ServletContextPool.get(pathContext);
332    
333                    if (!actions.equals(FILE_PERMISSION_ACTION_EXECUTE) &&
334                            (_workDir != null)) {
335    
336                            addPermission(permissions, _workDir, actions);
337                            addPermission(permissions, _workDir + "/-", actions);
338    
339                            if (servletContext != null) {
340                                    File tempDir = (File)servletContext.getAttribute(
341                                            JavaConstants.JAVAX_SERVLET_CONTEXT_TEMPDIR);
342    
343                                    String tempDirAbsolutePath = tempDir.getAbsolutePath();
344    
345                                    if (_log.isDebugEnabled()) {
346                                            _log.debug("Temp directory " + tempDirAbsolutePath);
347                                    }
348    
349                                    if (actions.equals(FILE_PERMISSION_ACTION_READ)) {
350                                            addPermission(permissions, tempDirAbsolutePath, actions);
351                                    }
352    
353                                    addPermission(permissions, tempDirAbsolutePath + "/-", actions);
354                            }
355                    }
356    
357                    if (!actions.equals(FILE_PERMISSION_ACTION_READ)) {
358                            return permissions;
359                    }
360    
361                    List<String> paths = new UniqueList<String>();
362    
363                    // JDK
364    
365                    // There may be JARs in the system library that are symlinked. We must
366                    // include their canonical paths or they will fail permission checks.
367    
368                    try {
369                            File file = new File(System.getProperty("java.home") + "/lib");
370    
371                            addCanonicalPaths(paths, file);
372                    }
373                    catch (IOException ioe) {
374                            _log.error(ioe, ioe);
375                    }
376    
377                    // Shared libs
378    
379                    if (Validator.isNotNull(_globalSharedLibDir)) {
380                            paths.add(_globalSharedLibDir + "-");
381                    }
382    
383                    // Plugin
384    
385                    if (_rootDir != null) {
386                            paths.add(_rootDir + "-");
387                    }
388    
389                    // Portal
390    
391                    addDefaultReadPaths(paths, ServerDetector.getServerId());
392    
393                    for (String path : paths) {
394                            addPermission(permissions, path, actions);
395                    }
396    
397                    return permissions;
398            }
399    
400            protected boolean hasDelete(Permission permission) {
401                    for (Permission deleteFilePermission : _deletePermissions) {
402                            if (deleteFilePermission.implies(permission)) {
403                                    return true;
404                            }
405                    }
406    
407                    if (ServerDetector.isResin()) {
408                            for (int i = 7;; i++) {
409                                    Class<?> callerClass = Reflection.getCallerClass(i);
410    
411                                    if (callerClass == null) {
412                                            return false;
413                                    }
414    
415                                    String callerClassName = callerClass.getName();
416    
417                                    if (callerClassName.equals(_CLASS_NAME_FILE_PATH)) {
418                                            String actualClassLocation = PACLClassUtil.getClassLocation(
419                                                    callerClass);
420                                            String expectedClassLocation = PathUtil.toUnixPath(
421                                                    System.getProperty("resin.home") + "/lib/resin.jar!/");
422    
423                                            return actualClassLocation.contains(expectedClassLocation);
424                                    }
425                            }
426                    }
427    
428                    return false;
429            }
430    
431            protected boolean hasExecute(Permission permission) {
432                    for (Permission executeFilePermission : _executePermissions) {
433                            if (executeFilePermission.implies(permission)) {
434                                    return true;
435                            }
436                    }
437    
438                    return false;
439            }
440    
441            protected boolean hasRead(Permission permission) {
442                    for (Permission readFilePermission : _readPermissions) {
443                            if (readFilePermission.implies(permission)) {
444                                    return true;
445                            }
446                    }
447    
448                    if (isJSPCompiler(permission.getName(), FILE_PERMISSION_ACTION_READ)) {
449                            return true;
450                    }
451    
452                    for (int i = 7;; i++) {
453                            Class<?> callerClass = Reflection.getCallerClass(i);
454    
455                            if (callerClass == null) {
456                                    return false;
457                            }
458    
459                            if ((callerClass == DirectServletRegistryImpl.class) ||
460                                    (callerClass == FileAvailabilityUtil.class)) {
461    
462                                    return true;
463                            }
464    
465                            if (ClassLoader.class.isAssignableFrom(callerClass)) {
466                                    String callerClassName = callerClass.getName();
467    
468                                    if (!callerClassName.equals(_CLASS_NAME_METHOD_UTIL)) {
469                                            return true;
470                                    }
471                            }
472    
473                            if (ServerDetector.isGlassfish()) {
474                                    Class<?> enclosingClass = callerClass.getEnclosingClass();
475    
476                                    if (enclosingClass != null) {
477                                            if ((enclosingClass.getEnclosingClass() ==
478                                                            URLClassLoader.class) &&
479                                                    CheckerUtil.isAccessControllerDoPrivileged(i + 1)) {
480    
481                                                    return true;
482                                            }
483                                    }
484                            }
485                            else if (ServerDetector.isResin()) {
486                                    String callerClassName = callerClass.getName();
487    
488                                    if (callerClassName.equals(_CLASS_NAME_FILE_PATH)) {
489                                            String actualClassLocation = PACLClassUtil.getClassLocation(
490                                                    callerClass);
491                                            String expectedClassLocation = PathUtil.toUnixPath(
492                                                    System.getProperty("resin.home") + "/lib/resin.jar!/");
493    
494                                            return actualClassLocation.contains(expectedClassLocation);
495                                    }
496                            }
497                    }
498            }
499    
500            protected boolean hasWrite(Permission permission) {
501                    for (Permission writeFilePermission : _writePermissions) {
502                            if (writeFilePermission.implies(permission)) {
503                                    return true;
504                            }
505                    }
506    
507                    if (ServerDetector.isResin()) {
508                            for (int i = 7;; i++) {
509                                    Class<?> callerClass = Reflection.getCallerClass(i);
510    
511                                    if (callerClass == null) {
512                                            return false;
513                                    }
514    
515                                    String callerClassName = callerClass.getName();
516    
517                                    if (callerClassName.equals(_CLASS_NAME_FILE_PATH)) {
518                                            String actualClassLocation = PACLClassUtil.getClassLocation(
519                                                    callerClass);
520                                            String expectedClassLocation = PathUtil.toUnixPath(
521                                                    System.getProperty("resin.home") + "/lib/resin.jar!/");
522    
523                                            return actualClassLocation.contains(expectedClassLocation);
524                                    }
525                            }
526                    }
527                    else if (ServerDetector.isWebSphere()) {
528                            if (isJSPCompiler(
529                                            permission.getName(), FILE_PERMISSION_ACTION_WRITE)) {
530    
531                                    return true;
532                            }
533                    }
534    
535                    return false;
536            }
537    
538            protected void initPermissions() {
539                    _deletePermissions = getPermissions(
540                            "security-manager-files-delete", FILE_PERMISSION_ACTION_DELETE);
541                    _executePermissions = getPermissions(
542                            "security-manager-files-execute", FILE_PERMISSION_ACTION_EXECUTE);
543                    _readPermissions = getPermissions(
544                            "security-manager-files-read", FILE_PERMISSION_ACTION_READ);
545                    _writePermissions = getPermissions(
546                            "security-manager-files-write", FILE_PERMISSION_ACTION_WRITE);
547            }
548    
549            private static final String _CLASS_NAME_FILE_PATH =
550                    "com.caucho.vfs.FilePath";
551    
552            private static final String _CLASS_NAME_METHOD_UTIL =
553                    "sun.reflect.misc.MethodUtil";
554    
555            private static Log _log = LogFactoryUtil.getLog(FileChecker.class);
556    
557            private String[] _defaultReadPathsFromArray;
558            private String[] _defaultReadPathsToArray;
559            private List<Permission> _deletePermissions;
560            private List<Permission> _executePermissions;
561            private String _globalSharedLibDir =
562                    PropsValues.LIFERAY_LIB_GLOBAL_SHARED_DIR;
563            private String _portalDir = PropsValues.LIFERAY_WEB_PORTAL_DIR;
564            private List<Permission> _readPermissions;
565            private String _rootDir;
566            private String _workDir;
567            private List<Permission> _writePermissions;
568    
569    }