From aa26af977d438de7985afab881f927bf2e63b47f Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Tue, 31 Mar 2009 11:35:46 +0000 Subject: [PATCH] New options for filesystme stream-wrapper: - all: false (default) =3D ignore files starting with a dot '.', true =3D show all files (. and .. are always ignored!) - exec: false (default) =3D do NOT allow to upload or modify scripts, = true =3D allow it (if docroot is mounted, this allows to run scripts!) --> deny_script method was added to egw_vfs and calls to it from filemanager Other fixes: - missing write rights of the webserver were not removed from perms (causing warnings to be displayed in the ui) - rename was not working due to typos --> should be backported for obvious reasons to 1.6 --- filemanager/inc/class.filemanager_ui.inc.php | 13 ++- filemanager/setup/egw_de.lang | 1 + filemanager/setup/egw_en.lang | 1 + phpgwapi/inc/class.egw_vfs.inc.php | 13 ++- .../class.filesystem_stream_wrapper.inc.php | 95 ++++++++++++++++--- 5 files changed, 108 insertions(+), 15 deletions(-) diff --git a/filemanager/inc/class.filemanager_ui.inc.php b/filemanager/inc/class.filemanager_ui.inc.php index ef1d0a0556..49465a52dc 100644 --- a/filemanager/inc/class.filemanager_ui.inc.php +++ b/filemanager/inc/class.filemanager_ui.inc.php @@ -5,7 +5,7 @@ * @link http://www.egroupware.org * @package filemanager * @author Ralf Becker - * @copyright (c) 2008 by Ralf Becker + * @copyright (c) 2008-9 by Ralf Becker * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @version $Id$ */ @@ -259,7 +259,12 @@ class filemanager_ui // strip '?', '/' and '#' from filenames, as they are forbidden for sqlfs / make problems $path = egw_vfs::concat($dir,str_replace(array('?','/','#'),'',$name)); - if (egw_vfs::stat($path)) + if(egw_vfs::deny_script($path)) + { + $response->addAlert(lang('You are NOT allowed to upload a script!')); + $response->addScript("document.getElementById('$id').value='';"); + } + elseif (egw_vfs::stat($path)) { if (egw_vfs::is_dir($path)) { @@ -640,6 +645,10 @@ class filemanager_ui else { $msg .= lang('Rename of %1 to %2 failed!',$path,$to).' '; + if (egw_vfs::deny_script($to)) + { + $msg .= lang('You are NOT allowed to upload a script!').' '; + } } } elseif ($name[0] == '#' || $name == 'comment') diff --git a/filemanager/setup/egw_de.lang b/filemanager/setup/egw_de.lang index 37ae0b674f..fbb5a5bccd 100644 --- a/filemanager/setup/egw_de.lang +++ b/filemanager/setup/egw_de.lang @@ -158,6 +158,7 @@ updated comment for %1 filemanager de Kommentar für %1 aktualisiert used space filemanager de Benutzter Platz users and groups filemanager de Benutzer und Gruppen wrong username or password! filemanager de Falscher Benutzername oder Passwort! +you are not allowed to upload a script! filemanager de Sie dürfen KEINE Skripte hochladen! you do not have access to %1 filemanager de Sie besitzen keine Zugriffsrechte für %1 you need to select an owner! filemanager de Sie müssen einen Eigentümer auswählen! you need to select some files first! filemanager de Sie müssen zuerst die Dateien auswählen! diff --git a/filemanager/setup/egw_en.lang b/filemanager/setup/egw_en.lang index 78e3d3e9ed..8aa6504b58 100644 --- a/filemanager/setup/egw_en.lang +++ b/filemanager/setup/egw_en.lang @@ -158,6 +158,7 @@ updated comment for %1 filemanager en Updated comment for %1 used space filemanager en Used space users and groups filemanager en Users and groups wrong username or password! filemanager en Wrong username or password! +you are not allowed to upload a script! filemanager en You are NOT allowed to upload a script! you do not have access to %1 filemanager en You do not have access to %1 you need to select an owner! filemanager en You need to select an owner! you need to select some files first! filemanager en You need to select some files first! diff --git a/phpgwapi/inc/class.egw_vfs.inc.php b/phpgwapi/inc/class.egw_vfs.inc.php index d32c9c3a7a..33b0a03bda 100644 --- a/phpgwapi/inc/class.egw_vfs.inc.php +++ b/phpgwapi/inc/class.egw_vfs.inc.php @@ -7,7 +7,7 @@ * @package api * @subpackage vfs * @author Ralf Becker - * @copyright (c) 2008 by Ralf Becker + * @copyright (c) 2008-9 by Ralf Becker * @version $Id$ */ @@ -666,6 +666,17 @@ class egw_vfs extends vfs_stream_wrapper return self::is_readable($path,1); } + /** + * Check if path is a script and write access would be denied by backend + * + * @param string $path + * @return boolean true if $path is a script AND exec mount-option is NOT set, false otherwise + */ + static function deny_script($path) + { + return self::_call_on_backend('deny_script',array($path),true); + } + /** * Set or delete extended acl for a given path and owner (or delete them if is_null($rights) * diff --git a/phpgwapi/inc/class.filesystem_stream_wrapper.inc.php b/phpgwapi/inc/class.filesystem_stream_wrapper.inc.php index cf2f9d81cc..4d8e44d19c 100644 --- a/phpgwapi/inc/class.filesystem_stream_wrapper.inc.php +++ b/phpgwapi/inc/class.filesystem_stream_wrapper.inc.php @@ -7,7 +7,7 @@ * @package api * @subpackage vfs * @author Ralf Becker - * @copyright (c) 2008 by Ralf Becker + * @copyright (c) 2008-9 by Ralf Becker * @version $Id$ */ @@ -21,6 +21,9 @@ * - user: uid or user-name owning the path, default root * - group: gid or group-name owning the path, default root * - mode: mode bit for the path, default 0005 (read and execute for nobody) + * - all: false (default) = ignore files starting with a dot '.', true = show all files (. and .. are always ignored!) + * - exec: false (default) = do NOT allow to upload or modify scripts, true = allow it (if docroot is mounted, this allows to run scripts!) + * scripts are considered every file having a script-extension (eg. .php, .pl, .py), defined with SCRIPT_EXTENSION_PREG constant * * @link http://www.php.net/manual/en/function.stream-wrapper-register.php */ @@ -79,6 +82,13 @@ class filesystem_stream_wrapper implements iface_stream_wrapper */ private $opened_dir_url; + /** + * Should dir show all files, or only the ones NOT starting with a dot (. and .. are never shown) + * + * @var boolean + */ + private $dir_show_all = false; + /** * How much should be logged to the apache error-log * @@ -88,6 +98,11 @@ class filesystem_stream_wrapper implements iface_stream_wrapper */ const LOG_LEVEL = 1; + /** + * Regular expression identifying scripts, to NOT allow updating them if exec mount option is NOT set + */ + const SCRIPT_EXTENSIONS_PREG = '/\.(php[0-9]*|pl|py)$/'; + /** * This method is called immediately after your stream object is created. * @@ -129,10 +144,20 @@ class filesystem_stream_wrapper implements iface_stream_wrapper } return false; } + if ($mode != 'r' && self::deny_script($url)) + { + if (self::LOG_LEVEL) error_log(__METHOD__."($url,$mode,$options) permission denied, file is a script!"); + if (!($options & STREAM_URL_STAT_QUIET)) + { + trigger_error(__METHOD__."($url,$mode,$options) permission denied, file is a script!",E_USER_WARNING); + } + return false; + } // open the "real" file - if (!($this->opened_stream = fopen(parse_url($url,PHP_URL_PATH),$mode,$options))) + if (!($this->opened_stream = fopen($path=parse_url($url,PHP_URL_PATH),$mode,$options))) { + if (self::LOG_LEVEL) error_log(__METHOD__."($url,$mode,$options) fopen('$path','$mode',$options) returned false!"); return false; } $this->opened_stream_url = $url; @@ -303,21 +328,27 @@ class filesystem_stream_wrapper implements iface_stream_wrapper // check access rights if (!($from_stat = self::url_stat($url_from,0)) || !egw_vfs::check_access(egw_vfs::dirname($url_from),egw_vfs::WRITABLE)) { - if (self::LOG_LEVEL) error_log(__METHOD__."($url_from,$url_to): $path_from permission denied!"); + if (self::LOG_LEVEL) error_log(__METHOD__."($url_from,$url_to): $from[path] permission denied!"); return false; // no permission or file does not exist } + $to_dir = egw_vfs::dirname($url_to); if (!egw_vfs::check_access($to_dir,egw_vfs::WRITABLE,$to_dir_stat = self::url_stat($to_dir,0))) { - if (self::LOG_LEVEL) error_log(__METHOD__."($url_from,$url_to): $path_to permission denied!"); + if (self::LOG_LEVEL) error_log(__METHOD__."($url_from,$url_to): $to_dir permission denied!"); return false; // no permission or parent-dir does not exist } + if (self::deny_script($url_to)) + { + if (self::LOG_LEVEL) error_log(__METHOD__."($url_from,$url_to) permission denied, file is a script!"); + return false; + } // the filesystem stream-wrapper does NOT allow to rename files to directories, as this makes problems // for our vfs too, we abort here with an error, like the filesystem one does - if (($to_stat = self::url_stat($path_to,0)) && + if (($to_stat = self::url_stat($to['path'],0)) && ($to_stat['mime'] === self::DIR_MIME_TYPE) !== ($from_stat['mime'] === self::DIR_MIME_TYPE)) { $is_dir = $to_stat['mime'] === self::DIR_MIME_TYPE ? 'a' : 'no'; - if (self::LOG_LEVEL) error_log(__METHOD__."($url_to,$url_from) $path_to is $is_dir directory!"); + if (self::LOG_LEVEL) error_log(__METHOD__."($url_to,$url_from) $to[path] is $is_dir directory!"); return false; // no permission or file does not exist } // if destination file already exists, delete it @@ -459,16 +490,19 @@ class filesystem_stream_wrapper implements iface_stream_wrapper */ function dir_opendir ( $url, $options ) { - error_log(__METHOD__."($url,$options)"); + if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$options)"); $this->opened_dir = null; $parts = parse_url($this->opened_dir_url = $url); + parse_str($parts['query'],$get); + $this->dir_show_all = (bool)$get['all']; // ToDo: check access rights if (!($this->opened_dir = opendir($parts['path']))) { + if (self::LOG_LEVEL > 0) error_log(__METHOD__."($url,$options) opendir('$parts[path]') failed!"); return false; } return true; @@ -504,7 +538,6 @@ class filesystem_stream_wrapper implements iface_stream_wrapper { $parts = parse_url($url); - //error_log(__METHOD__."('$path',$flags) calling stat($url)"); $stat = @stat($parts['path']); // suppressed the stat failed warnings if ($stat) @@ -513,12 +546,18 @@ class filesystem_stream_wrapper implements iface_stream_wrapper if (!self::parse_query($parts['query'],$uid,$gid,$mode)) { return false; + if (self::LOG_LEVEL > 0) error_log(__METHOD__."($url,$flags) can NOT self::parse_query('$parts[query]')!"); } $stat['uid'] = $stat[4] = $uid; $stat['gid'] = $stat[5] = $gid; $stat['mode'] = $stat[2] = $stat['mode'] & self::MODE_DIR ? self::MODE_DIR | $mode : self::MODE_FILE | ($mode & ~0111); + // write rights also depend on the write rights of the webserver + if (!is_writable($parts['path'])) + { + $stat['mode'] = $stat[2] = $stat['mode'] & ~0222; + } } - //echo __METHOD__."($url,$flags) path=$parts[path], mount_mode=".sprintf('0%o',$mode).", mode=".sprintf('0%o',$stat['mode']).'='.egw_vfs::int2mode($stat['mode'])."\n"; //print_r($stat); + if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$flags) path=$parts[path], mount_mode=".sprintf('0%o',$mode).", mode=".sprintf('0%o',$stat['mode']).'='.egw_vfs::int2mode($stat['mode'])); return $stat; } @@ -534,7 +573,19 @@ class filesystem_stream_wrapper implements iface_stream_wrapper */ function dir_readdir ( ) { - return readdir($this->opened_dir); + do { + $file = readdir($this->opened_dir); + + $ignore = !($file === false || // stop if no more dirs or + $file[0] != '.' || // file does NOT start with a dot '.' or + ($this->dir_show_all && $file != '.' && $file != '..' )); // file not . or .. or dir_show_all set + if (self::LOG_LEVEL > 1 && $ignore) error_log(__METHOD__.'() ignoring '.array2string($file)); + } + while ($ignore); + + if (self::LOG_LEVEL > 1) error_log(__METHOD__.'() returning '.array2string($file)); + + return $file; } /** @@ -559,11 +610,11 @@ class filesystem_stream_wrapper implements iface_stream_wrapper */ function dir_closedir ( ) { - $ret = closedir($this->opened_dir); + closedir($this->opened_dir); $this->opened_dir = $this->extra_dirs = null; - return $ret; + return true; } /** @@ -643,6 +694,26 @@ class filesystem_stream_wrapper implements iface_stream_wrapper //echo __METHOD__.'('.print_r($query,true).") uid=$uid, gid=$gid, mode=".sprintf('0%o',$mode)."\n"; return true; } + + /** + * Check if url is a script (self::$script_extentions) and exec mount option is NOT set + * + * @param string $url + * @return boolean true if $url is a script AND exec is NOT set, false otherwise + */ + static function deny_script($url) + { + $parts = parse_url($url); + parse_str($parts['query'],$get); + + $deny = !$get['exec'] && preg_match(self::SCRIPT_EXTENSIONS_PREG,$parts['path']); + + if (self::LOG_LEVEL > 1 || self::LOG_LEVEL > 0 && $deny) + { + error_log(__METHOD__."($url) returning ".array2string($deny)); + } + return $deny; + } } stream_register_wrapper(filesystem_stream_wrapper::SCHEME ,'filesystem_stream_wrapper');