More improvments of the sqlfs code and the command line interface:

- read rights are not checks in each traversed directory (via sql in a single query to locate the path)
- diropen additionally checks for execute rights
- fopen checks for read or write depending on the mode
- chmod, chgrp, chown methods in sqlfs and egw_vfs/vfs plus an egw_vfs::$is_root var used to grant root rights (no access controll and chown or chgrp without being the owner of a file)
- find method (some more params to come) to recursivly search and optionaly execute some callback
- egw_vfs::remove doing a "rm -r" / recursive remove or dirs and files
- new files or dirs inherit the perms and ownership from the parent directory (no umask)
- files/dirs the user has no read rights, in a directory where he has no write rights, get hidden (eg. not showing all the other users / groups home dirs
- many new cli commands (chmod, chgrp, chown, find), recursive option for most commands and the ability to use it with root rights, see the usage message if called without options
- "cp -r -p" to copy a whole tree incl. ownership and perms, eg. backing up /home to /backup
This commit is contained in:
Ralf Becker 2008-02-26 08:51:42 +00:00
parent d81d9bce03
commit 8afe9094b7
4 changed files with 919 additions and 132 deletions

View File

@ -13,6 +13,8 @@
chdir(dirname(__FILE__)); // to enable our relative pathes to work
error_reporting(error_reporting() & ~E_NOTICE);
if (isset($_SERVER['HTTP_HOST'])) // security precaution: forbit calling ls as web-page
{
die('<h1>'.basename(__FILE__).' must NOT be called as web-page --> exiting !!!</h1>');
@ -30,7 +32,7 @@ function user_pass_from_argv(&$account)
//print_r($account);
if (!($sessionid = $GLOBALS['egw']->session->create($account)))
{
echo "Wrong admin-account or -password !!!\n\n";
echo "Wrong user-account or -password !!!\n\n";
usage('',1);
}
return $sessionid;
@ -45,19 +47,26 @@ function user_pass_from_argv(&$account)
function usage($action=null,$ret=0)
{
$cmd = basename(__FILE__);
echo "Usage:\t$cmd URL [URL2 ...]\n";
echo "Usage:\t$cmd [-r|--recursive] URL [URL2 ...]\n";
echo "\t$cmd --cat URL [URL2 ...]\n";
echo "\t$cmd --cp URL-from URL-to\n";
echo "\t$cmd --cp URL-from [URL-from2 ...] URL-to-directory\n";
echo "\t$cmd --rm URL [URL2 ...]\n";
echo "\t$cmd --cp [-r|--recursive] [-p|--perms] URL-from URL-to\n";
echo "\t$cmd --cp [-r|--recursive] [-p|--perms] URL-from [URL-from2 ...] URL-to-directory\n";
echo "\t$cmd --rm [-r|--recursive] URL [URL2 ...]\n";
echo "\t$cmd --mkdir [-p|--parents] URL [URL2 ...]\n";
echo "\t$cmd --rmdir URL [URL2 ...]\n";
echo "\t$cmd --touch [-d|--date time] URL [URL2 ...]\n";
echo "URL: oldvfs://user:password@domain/home/user/file, /dir/file, ...\n";
echo "\t$cmd --touch [-r|--recursive] [-d|--date time] URL [URL2 ...]\n";
echo "\t$cmd --chmod [-r|--recursive] mode=[ugoa]*[+-=][rwx]+,... URL [URL2 ...]\n";
echo "\t$cmd --chown [-r|--recursive] user URL [URL2 ...]\n";
echo "\t$cmd --chgrp [-r|--recursive] group URL [URL2 ...]\n";
echo "\t$cmd --find URL [URL2 ...] [options to come]\n";
echo "\nURL: {vfs|sqlfs|oldvfs}://user:password@domain/home/user/file, /dir/file, ...\n";
echo "\nUse root_{header-admin|config-user} as user and according password for root access (no user specific access control and chown).\n\n";
exit;
}
$long = $numeric = $recursive = false;
$long = $numeric = $recursive = $perms = false;
$argv = $_SERVER['argv'];
$cmd = basename(array_shift($argv),'.php');
@ -84,10 +93,20 @@ foreach($argv as $key => $option)
continue 2; // switch is counting too!
case '-r': case '--recursive':
case '-p': case '--parents':
$recursive = true;
continue 2; // switch is counting too!
case '-p': case '--parents': case '--perms':
if ($cmd == 'cp')
{
$perms = true;
}
else
{
$recursive = true;
}
continue 2; // switch is counting too!
case '-d': case '--date':
$time = strtotime($argv[$key+1]);
unset($argv[$key+1]);
@ -101,6 +120,10 @@ foreach($argv as $key => $option)
case '--mkdir': // make directories
case '--rename':// rename
case '--touch': // touch
case '--chmod': // chmod
case '--chown': // chown (requires root)
case '--chgrp': // chgrp
case '--find':
$cmd = substr($option,2);
continue 2; // switch is counting too!
}
@ -110,8 +133,12 @@ $argc = count($argv);
switch($cmd)
{
case 'find':
do_find($argv);
break;
case 'cp':
do_cp($argv);
do_cp($argv,$recursive,$perms);
break;
case 'rename':
@ -122,15 +149,26 @@ switch($cmd)
break;
default:
while($url = array_shift($argv))
while($argv)
{
$url = array_shift($argv);
load_wrapper($url);
//echo "$cmd $url (long=".(int)$long.", numeric=".(int)$numeric.")\n";
switch($cmd)
{
case 'rm':
if ($recursive && class_exists('egw_vfs'))
{
array_unshift($argv,$url);
egw_vfs::remove($argv);
$argv = array();
}
else
{
unlink($url);
}
break;
case 'rmdir':
@ -142,28 +180,106 @@ switch($cmd)
break;
case 'touch':
case 'chmod':
case 'chown':
case 'chgrp':
switch($cmd)
{
case 'touch':
$params = array($url,$time);
break;
case 'chmod':
if (!isset($mode))
{
$mode = $url; // first param is mode
$url = array_shift($argv);
}
if (parse_url($url,PHP_URL_SCHEME)) load_wrapper($url); // cant use stat or egw_vfs::mode2int otherwise!
if (strpos($mode,'+') !== false || strpos($mode,'-') !== false)
{
$stat = stat($url);
$set = $stat['mode'];
}
else
{
$set = 0;
}
if (!class_exists('egw_vfs'))
{
die("chmod only implemented for eGW streams!"); // dont want to repeat the code here
}
$set = egw_vfs::mode2int($mode,$set);
$params = array($url,$set);
break;
case 'chown':
case 'chgrp':
$type = $cmd == 'chgrp' ? 'group' : 'user';
if (!isset($owner))
{
$owner = $url; // first param is owner/group
$url = array_shift($argv);
if (parse_url($url,PHP_URL_SCHEME)) load_wrapper($url); // we need the header loaded
if ($owner == 'root')
{
$owner = 0;
}
elseif (!is_numeric($owner))
{
if (!is_object($GLOBALS['egw']))
{
die("only numeric user/group-id's allowed for non eGW streams!");
}
if (!($owner = $GLOBALS['egw']->accounts->name2id($owner_was=$owner,'account_lid',$type[0])) ||
($owner < 0) != ($cmd == 'chgrp'))
{
die("Unknown $type '$owner_was'!");
}
}
elseif($owner && is_object($GLOBALS['egw']) && (!$GLOBALS['egw']->accounts->id2name($owner) ||
($owner < 0) != ($cmd == 'chgrp')))
{
die("Unknown $type '$owner_was'!");
}
}
$params = array($url,$owner);
break;
}
if (($scheme = parse_url($url,PHP_URL_SCHEME)))
{
load_wrapper($url);
if (class_exists($class = $scheme.'_stream_wrapper') && method_exists($class,'touch'))
{
call_user_func(array($scheme.'_stream_wrapper','touch'),$url,$time);
$cmd = array($scheme.'_stream_wrapper',$cmd);
}
else
{
die("Can't touch for scheme $scheme!\n");
die("Can't $cmd for scheme $scheme!\n");
}
}
else
if ($recursive && class_exists('egw_vfs'))
{
touch($url,$time);
array_unshift($argv,$url);
$params = array($argv,null,$cmd,$params[1]);
$cmd = array('egw_vfs','find');
$argv = array(); // we processed all url's
}
//echo "calling cmd=".print_r($cmd,true).", params=".print_r($params,true)."\n";
call_user_func_array($cmd,$params);
break;
case 'cat':
case 'ls':
default:
if (is_dir($url) && ($dir = opendir($url)))
// recursive ls atm only for vfs://
if ($cmd != 'cat' && $recursive && class_exists('egw_vfs'))
{
load_wrapper($url);
array_unshift($argv,$url);
egw_vfs::find($argv,array('dirs_last'=>true),'do_stat',array($long,$numeric,true));
$argv = array();
}
elseif (is_dir($url) && ($dir = opendir($url)))
{
if ($argc)
{
@ -235,8 +351,30 @@ function load_wrapper($url)
)
);
if (substr($GLOBALS['egw_login_data']['login'],0,5) != 'root_')
{
include('../header.inc.php');
}
else
{
$GLOBALS['egw_info']['flags']['currentapp'] = 'login';
include('../header.inc.php');
if ($GLOBALS['egw_login_data']['login'] == 'root_'.$GLOBALS['egw_info']['server']['header_admin_user'] &&
_check_pw($GLOBALS['egw_info']['server']['header_admin_password'],$GLOBALS['egw_login_data']['passwd']) ||
$GLOBALS['egw_login_data']['login'] == 'root_'.$GLOBALS['egw_domain'][$_GET['domain']]['config_user'] &&
_check_pw($GLOBALS['egw_domain'][$_GET['domain']]['config_passwd'],$GLOBALS['egw_login_data']['passwd']))
{
echo "\nRoot access granted!\n";
egw_vfs::$is_root = true;
}
else
{
die("Unknown user or password!\n");
}
set_exception_handler('cli_exception_handler'); // otherwise we get html!
}
}
require_once(EGW_API_INC.'/class.'.$scheme.'_stream_wrapper.inc.php');
break;
case '': // default scheme is file and alsways available
@ -250,6 +388,23 @@ function load_wrapper($url)
}
}
/**
* Check password against a md5 hash or cleartext password
*
* @param string $hash_or_cleartext
* @param string $pw
* @return boolean
*/
function _check_pw($hash_or_cleartext,$pw)
{
//echo "_check_pw($hash_or_cleartext,$pw) md5=".md5($pw)."\n";
if (preg_match('/^[0-9a-f]{32}$/',$hash_or_cleartext))
{
return $hash_or_cleartext == md5($pw);
}
return $hash_or_cleartext == $pw;
}
/**
* Give the stats for one file
*
@ -257,16 +412,27 @@ function load_wrapper($url)
* @param boolean $long=false true=long listing with owner,group,size,perms, default false only filename
* @param boolean $numeric=false true=give numeric uid&gid, else resolve the id to a name
*/
function do_stat($url,$long=false,$numeric=false)
function do_stat($url,$long=false,$numeric=false,$full_path=false)
{
//echo "do_stat($url,$long,$numeric)\n";
$bname = basename(parse_url($url,PHP_URL_PATH));
//echo "do_stat($url,$long,$numeric,$full_path)\n";
$bname = parse_url($url,PHP_URL_PATH);
if (!$full_path)
{
$bname = basename($bname);
}
if ($long && ($stat = stat($url)))
{
//print_r($stat);
//echo $url; print_r($stat);
$perms = verbosePerms($stat['mode']);
if (class_exists('egw_vfs'))
{
$perms = egw_vfs::int2mode($stat['mode']);
}
else
{
$perms = int2mode($stat['mode']);
}
if ($numeric)
{
$uid = $stat['uid'];
@ -276,14 +442,18 @@ function do_stat($url,$long=false,$numeric=false)
{
if ($stat['uid'])
{
$uid = isset($GLOBALS['egw']) ? $GLOBALS['egw']->accounts->id2name($stat['uid']) : posix_getpwuid($stat['uid']);
$uid = isset($GLOBALS['egw']) ? $GLOBALS['egw']->accounts->id2name($stat['uid']) :
(function_exists('posix_getpwuid') ? posix_getpwuid($stat['uid']) : $stat['uid']);
if (is_array($uid)) $uid = $uid['name'];
if (empty($uid)) $uid = $stat['uid'];
}
if (!isset($uid)) $uid = 'root';
if ($stat['gid'])
{
$gid = isset($GLOBALS['egw']) ? $GLOBALS['egw']->accounts->id2name(-abs($stat['gid'])) : posix_getgrgid($stat['gid']);
$gid = isset($GLOBALS['egw']) ? $GLOBALS['egw']->accounts->id2name(-abs($stat['gid'])) :
(function_exists('posix_getgrgid') ? posix_getgrgid($stat['gid']) : $stat['gid']);
if (is_array($gid)) $gid = $gid['name'];
if (empty($gid)) $gid = $stat['gid'];
}
if (!isset($gid)) $gid = 'root';
}
@ -306,33 +476,147 @@ function hsize($size)
return sprintf('%3.1lfM',(float)$size/(1024*1024));
}
function verbosePerms( $in_Perms )
function do_cp($argv,$recursive=false,$perms=false)
{
if($in_Perms & 0x1000) // FIFO pipe
$to = array_pop($argv);
load_wrapper($to);
$to_exists = file_exists($to);
if (count($argv) > 1 && $to_exists && !is_dir($to))
{
usage(null,4);
}
foreach($argv as $from)
{
if (is_dir($from) && (!file_exists($to) || is_dir($to)) && $recursive && class_exists('egw_vfs'))
{
foreach(egw_vfs::find($from) as $f)
{
$t = $to.substr($f,strlen($from));
if (is_dir($f))
{
++$anz_dirs;
mkdir($t);
}
else
{
++$anz_files;
_cp($f,$t);
}
if ($perms) _cp_perms($f,$t);
}
echo ($anz_dirs?"$anz_dirs dir(s) created and ":'')."$anz_files file(s) copied.\n";
}
else
{
_cp($from,$to,true);
if ($perms) _cp_perms($from,$to);
}
}
}
function _cp($from,$to,$verbose=false,$perms=false)
{
load_wrapper($from);
if (is_dir($to) || !file_exists($to) && is_dir($from) && $mkdir)
{
$path = parse_url($from,PHP_URL_PATH);
if (is_dir($to)) $to .= '/'.basename($path);
}
if (!($from_fp = fopen($from,'r')))
{
die("File $from not found!\n");
}
if (!($to_fp = fopen($to,'w')))
{
die("Can't open $to for writing!\n");
}
$count = stream_copy_to_stream($from_fp,$to_fp);
if ($verbose) echo hsize($count)." bytes written to $to\n";
fclose($from_fp);
if (!fclose($to_fp))
{
die("Error closing $to!\n");
}
}
function _cp_perms($from,$to)
{
if (($from_stat = stat($from)) && ($to_stat = stat($to)))
{
foreach(array(
'mode' => 'chmod',
'uid' => 'chown',
'gid' => 'chgrp',
) as $perm => $cmd)
{
if ($from_stat[$perm] != $to_stat[$perm])
{
//echo "egw_vfs::$cmd($to,{$from_stat[$perm]}\n";
call_user_func(array('egw_vfs',$cmd),$to,$from_stat[$perm]);
}
}
}
}
function do_find($bases)
{
foreach($bases as $url)
{
load_wrapper($url);
}
foreach(egw_vfs::find($bases) as $path)
{
echo "$path\n";
}
}
function cli_exception_handler(Exception $e)
{
echo $e->getMessage()."\n";
exit($e->getCode());
}
/**
* Convert a numerical mode to a symbolic mode-string
*
* @param int $mode
* @return string
*/
function int2mode( $mode )
{
if($mode & 0x1000) // FIFO pipe
{
$sP = 'p';
}
elseif($in_Perms & 0x2000) // Character special
elseif($mode & 0x2000) // Character special
{
$sP = 'c';
}
elseif($in_Perms & 0x4000) // Directory
elseif($mode & 0x4000) // Directory
{
$sP = 'd';
}
elseif($in_Perms & 0x6000) // Block special
elseif($mode & 0x6000) // Block special
{
$sP = 'b';
}
elseif($in_Perms & 0x8000) // Regular
elseif($mode & 0x8000) // Regular
{
$sP = '-';
}
elseif($in_Perms & 0xA000) // Symbolic Link
elseif($mode & 0xA000) // Symbolic Link
{
$sP = 'l';
}
elseif($in_Perms & 0xC000) // Socket
elseif($mode & 0xC000) // Socket
{
$sP = 's';
}
@ -342,66 +626,22 @@ function verbosePerms( $in_Perms )
}
// owner
$sP .= (($in_Perms & 0x0100) ? 'r' : '-') .
(($in_Perms & 0x0080) ? 'w' : '-') .
(($in_Perms & 0x0040) ? (($in_Perms & 0x0800) ? 's' : 'x' ) :
(($in_Perms & 0x0800) ? 'S' : '-'));
$sP .= (($mode & 0x0100) ? 'r' : '-') .
(($mode & 0x0080) ? 'w' : '-') .
(($mode & 0x0040) ? (($mode & 0x0800) ? 's' : 'x' ) :
(($mode & 0x0800) ? 'S' : '-'));
// group
$sP .= (($in_Perms & 0x0020) ? 'r' : '-') .
(($in_Perms & 0x0010) ? 'w' : '-') .
(($in_Perms & 0x0008) ? (($in_Perms & 0x0400) ? 's' : 'x' ) :
(($in_Perms & 0x0400) ? 'S' : '-'));
$sP .= (($mode & 0x0020) ? 'r' : '-') .
(($mode & 0x0010) ? 'w' : '-') .
(($mode & 0x0008) ? (($mode & 0x0400) ? 's' : 'x' ) :
(($mode & 0x0400) ? 'S' : '-'));
// world
$sP .= (($in_Perms & 0x0004) ? 'r' : '-') .
(($in_Perms & 0x0002) ? 'w' : '-') .
(($in_Perms & 0x0001) ? (($in_Perms & 0x0200) ? 't' : 'x' ) :
(($in_Perms & 0x0200) ? 'T' : '-'));
$sP .= (($mode & 0x0004) ? 'r' : '-') .
(($mode & 0x0002) ? 'w' : '-') .
(($mode & 0x0001) ? (($mode & 0x0200) ? 't' : 'x' ) :
(($mode & 0x0200) ? 'T' : '-'));
return $sP;
}
function do_cp($argv)
{
$to = array_pop($argv);
load_wrapper($to);
if (count($argv) > 1 && !is_dir($to))
{
usage(null,4);
}
if (count($argv) > 1)
{
foreach($argv as $from)
{
do_cp(array($from,$to));
}
return;
}
$from = array_shift($argv);
load_wrapper($from);
if (!($from_fp = fopen($from,'r')))
{
die("File $from not found!\n");
}
if (is_dir($to))
{
$path = parse_url($from,PHP_URL_PATH);
$to .= '/'.basename($path);
}
if (!($to_fp = fopen($to,'w')))
{
die("Can't open $to for writing!\n");
}
$count = stream_copy_to_stream($from_fp,$to_fp);
echo hsize($count)." bytes written to $to\n";
fclose($from_fp);
if (!fclose($to_fp))
{
die("Error closing $to!\n");
}
}

View File

@ -59,9 +59,31 @@
*/
class egw_vfs extends vfs_stream_wrapper
{
const EXECUTABLE = 4;
const READABLE = 2;
const WRITABLE = 1;
/**
* Readable bit, for dirs traversable
*/
const READABLE = 4;
/**
* Writable bit, for dirs delete or create files in that dir
*/
const WRITABLE = 2;
/**
* Excecutable bit, here only use to check if user is allowed to search dirs
*/
const EXECUTABLE = 1;
/**
* Current user has root rights, no access checks performed!
*
* @var boolean
*/
static $is_root = false;
/**
* Current user id, in case we ever change if away from $GLOBALS['egw_info']['user']['account_id']
*
* @var int
*/
static $user;
/**
* fopen working on just the eGW VFS
*
@ -188,6 +210,131 @@ class egw_vfs extends vfs_stream_wrapper
return true;
}
/**
* find = recursive search over the filesystem
*
* @param string/array $base base of the search
* @param array $params=null
* @param string/array/true $exec=null function to call with each found file or dir as first param or
* true to return file => stat pairs
* @param array $exec_params=null further params for exec as array, path is always the first param!
* @return array of pathes if no $exec, otherwise path => stat pairs
*/
function find($base,$params=null,$exec=null,$exec_params=null)
{
//error_log(__METHOD__."(".print_r($base,true).",".print_r($params,true).",".print_r($exec,true).",".print_r($exec_params,true).")\n");
$type = $params['type']; // 'd' or 'f'
$dirs_last = $params['dirs_last']; // list dirs after the files they contain
if (!is_array($base))
{
$base = array($base);
}
foreach($base as $path)
{
// check our fstab if we need to add some of the mountpoints
$basepath = parse_url($path,PHP_URL_PATH);
foreach(self::$fstab as $mounted => $src_url)
{
if (dirname($mounted) == $basepath)
{
$base[] = $mounted;
}
}
}
$result = array();
foreach($base as $path)
{
/* if (($scheme = parse_url($path,PHP_URL_SCHEME)))
{
self::load_wrapper();
}*/
if (!$type || ($type[0]=='d') == is_dir($path))
{
if (!($stat = self::url_stat($path,0))) continue;
if (!$dirs_last || !is_dir($path))
{
$result[$path] = $stat;
}
}
if (is_dir($path) && ($dir = opendir($path)))
{
while($file = readdir($dir))
{
$file = $path.'/'.$file;
if (!$type || ($type[0]=='d') == is_dir($file))
{
if (!($stat = self::url_stat($file,0))) continue;
$result[$file] = $stat;
}
if (is_dir($file))
{
foreach(self::find($file,$params,true) as $p => $s)
{
unset($result[$p]);
$result[$p] = $s;
}
}
}
closedir($dir);
if ($dirs_last)
{
$result[$path] = $stat;
}
}
}
//_debug_array($result);
if ($exec !== true && is_callable($exec))
{
if (!is_array($exec_params))
{
$exec_params = is_null($exec_params) ? array() : array($exec_params);
}
foreach($result as $path => &$stat)
{
$params = $exec_params;
array_unshift($params,$path);
//echo "calling ".print_r($exec,true).print_r($params,true)."\n";
$stat = call_user_func_array($exec,$params);
}
return $result;
}
if ($exec !== true)
{
return array_keys($result);
}
return $result;
}
/**
* Recursiv remove all given url's, including it's content if they are files
*
* @param string/array $urls url or array of url's
* @return array
*/
static function remove($urls)
{
return self::find($urls,array('dirs_last'=>true),array(__CLASS__,'_rm_rmdir'));
}
/**
* Helper function for remove: either rmdir or unlink given url (depending if it's a dir or file)
*
* @param string $url
* @return boolean
*/
static function _rm_rmdir($url)
{
if (is_dir($url))
{
return rmdir($url);
}
return unlink($url);
}
/**
* The stream_wrapper interface checks is_{readable|writable|executable} against the webservers uid,
* which is wrong in case of our vfs, as we use the current users id and memberships
@ -210,37 +357,42 @@ class egw_vfs extends vfs_stream_wrapper
* which is wrong in case of our vfs, as we use the current users id and memberships
*
* @param array $stat
* @param int $check mode to check: 4 = read, 2 = write, 1 = executable
* @param int $check mode to check: one or more or'ed together of: 4 = read, 2 = write, 1 = executable
* @return boolean
*/
static function check_access($stat,$check)
{
//error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check)");
if (self::$is_root)
{
return true;
}
if (!$stat)
{
//error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) no stat array!");
return false; // file not found
}
// check if other rights grant access
if ($stat['mode'] & $check)
if (($stat['mode'] & $check) == $check)
{
error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via other rights!");
//error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via other rights!");
return true;
}
// check if there's owner access and we are the owner
if (($stat['mode'] & ($check << 6)) && $stat['uid'] && $stat['uid'] == $GLOBALS['egw_info']['user']['account_id'])
if (($stat['mode'] & ($check << 6)) == ($check << 6) && $stat['uid'] && $stat['uid'] == self::$user)
{
//error_log(__METHOD__."(stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via owner rights!");
return true;
}
// check if there's a group access and we have the right membership
if (($stat['mode'] & ($check << 3)) && $stat['gid'])
if (($stat['mode'] & ($check << 3)) == ($check << 3) && $stat['gid'])
{
static $memberships;
if (is_null($memberships))
{
$memberships = $GLOBALS['egw']->accounts->memberships($GLOBALS['egw_info']['user']['account_id'],true);
$memberships = $GLOBALS['egw']->accounts->memberships(self::$user,true);
}
if (in_array(-abs($stat['gid']),$memberships))
{
@ -285,4 +437,131 @@ class egw_vfs extends vfs_stream_wrapper
{
}
/**
* Convert a symbolic mode string or octal mode to an integer
*
* @param string/int $set comma separated mode string to set [ugo]+[+=-]+[rwx]+
* @param int $mode=0 current mode of the file, necessary for +/- operation
* @return int
*/
static function mode2int($set,$mode=0)
{
if (is_int($set)) // already an integer
{
return $set;
}
if (is_numeric($set)) // octal string
{
//error_log(__METHOD__."($set,$mode) returning ".(int)base_convert($set,8,10));
return (int)base_convert($set,8,10); // convert octal to decimal
}
foreach(explode(',',$set) as $s)
{
if (!preg_match($use='/^([ugoa]*)([+=-]+)([rwx]+)$/',$s,$matches))
{
$use = str_replace(array('/','^','$','(',')'),'',$use);
throw new egw_exception_wrong_userinput("$s is not an allowed mode, use $use !");
}
$base = (strpos($matches[3],'r') !== false ? self::READABLE : 0) |
(strpos($matches[3],'w') !== false ? self::WRITABLE : 0) |
(strpos($matches[3],'x') !== false ? self::EXECUTABLE : 0);
for($n = $m = 0; $n < strlen($matches[1]); $n++)
{
switch($matches[1][$n])
{
case 'o':
$m |= $base;
break;
case 'g':
$m |= $base << 3;
break;
case 'u':
$m |= $base << 6;
break;
default:
case 'a':
$m = $base | ($base << 3) | ($base << 6);
}
}
switch($matches[2])
{
case '+':
$mode |= $m;
break;
case '=':
$mode = $m;
break;
case '-':
$mode &= ~$m;
}
}
//error_log(__METHOD__."($set,) returning ".sprintf('%o',$mode));
return $mode;
}
/**
* Convert a numerical mode to a symbolic mode-string
*
* @param int $mode
* @return string
*/
static function int2mode( $mode )
{
if($mode & 0x1000) // FIFO pipe
{
$sP = 'p';
}
elseif($mode & 0x2000) // Character special
{
$sP = 'c';
}
elseif($mode & 0x4000) // Directory
{
$sP = 'd';
}
elseif($mode & 0x6000) // Block special
{
$sP = 'b';
}
elseif($mode & 0x8000) // Regular
{
$sP = '-';
}
elseif($mode & 0xA000) // Symbolic Link
{
$sP = 'l';
}
elseif($mode & 0xC000) // Socket
{
$sP = 's';
}
else // UNKNOWN
{
$sP = 'u';
}
// owner
$sP .= (($mode & 0x0100) ? 'r' : '-') .
(($mode & 0x0080) ? 'w' : '-') .
(($mode & 0x0040) ? (($mode & 0x0800) ? 's' : 'x' ) :
(($mode & 0x0800) ? 'S' : '-'));
// group
$sP .= (($mode & 0x0020) ? 'r' : '-') .
(($mode & 0x0010) ? 'w' : '-') .
(($mode & 0x0008) ? (($mode & 0x0400) ? 's' : 'x' ) :
(($mode & 0x0400) ? 'S' : '-'));
// world
$sP .= (($mode & 0x0004) ? 'r' : '-') .
(($mode & 0x0002) ? 'w' : '-') .
(($mode & 0x0001) ? (($mode & 0x0200) ? 't' : 'x' ) :
(($mode & 0x0200) ? 'T' : '-'));
return $sP;
}
}
egw_vfs::$user = (int) $GLOBALS['egw_info']['user']['account_id'];

View File

@ -31,10 +31,6 @@
*/
class sqlfs_stream_wrapper implements iface_stream_wrapper
{
/**
* If this class should do the operations direct in the filesystem, instead of going through the vfs
*/
const USE_FILESYSTEM_DIRECT = true;
/**
* Mime type of directories, the old vfs uses 'Directory', while eg. WebDAV uses 'httpd/unix-directory'
*/
@ -193,12 +189,15 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
$values = array(
'fs_name' => basename($path),
'fs_dir' => $dir_stat['ino'],
// we use the mode of the dir, so files in group dirs stay accessible by all members
'fs_mode' => $dir_stat['mode'] & 0666,
'fs_uid' => $dir_stat['uid'],
// for the uid we use the uid of the dir if not 0=root or the current user otherwise
'fs_uid' => $dir_stat['uid'] ? $dir_stat['uid'] : egw_vfs::$user,
// we allways use the group of the dir
'fs_gid' => $dir_stat['gid'],
'fs_created' => self::_pdo_timestamp(time()),
'fs_modified' => self::_pdo_timestamp(time()),
'fs_creator' => $GLOBALS['egw_info']['user']['account_id'],
'fs_creator' => egw_vfs::$user,
);
foreach($values as $name => &$val)
{
@ -530,7 +529,18 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
$path = parse_url($url,PHP_URL_PATH);
$parent = self::url_stat(dirname($path),0);
if (self::url_stat($path,STREAM_URL_STAT_QUIET))
{
self::_remove_password($url);
if (self::LOG_LEVEL) error_log(__METHOD__."('$url',$mode,$options) already exist!");
if (!($options & STREAM_URL_STAT_QUIET))
{
trigger_error(__METHOD__."('$url',$mode,$options) already exist!",E_USER_WARNING);
}
return false;
}
$parent = self::url_stat(dirname($path),STREAM_URL_STAT_QUIET);
// check if we should also create all non-existing path components and our parent does not exist,
// if yes call ourself recursive with the parent directory
@ -545,7 +555,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
if (!$parent || !egw_vfs::check_access($parent,egw_vfs::WRITABLE))
{
self::_remove_password($url);
if (self::LOG_LEVEL) error_log(__METHOD__."($url) permission denied!");
if (self::LOG_LEVEL) error_log(__METHOD__."('$url',$mode,$options) permission denied!");
if (!($options & STREAM_URL_STAT_QUIET))
{
trigger_error(__METHOD__."('$url',$mode,$options) permission denied!",E_USER_WARNING);
@ -564,7 +574,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
':fs_mime' => self::DIR_MIME_TYPE,
':fs_created' => self::_pdo_timestamp(time()),
':fs_modified' => self::_pdo_timestamp(time()),
':fs_creator' => $GLOBALS['egw_info']['user']['account_id'],
':fs_creator' => egw_vfs::$user,
))) && $operation == self::STORE2FS)
{
mkdir(self::_fs_path($path));
@ -634,10 +644,21 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
$path = parse_url($url,PHP_URL_PATH);
if (!($stat = self::url_stat($path,0)))
if (!($stat = self::url_stat($path,STREAM_URL_STAT_QUIET)))
{
// file does not exist --> create an empty one
if (($f = fopen(self::SCHEME.'://default'.$path,'w')) && fclose($f))
{
if (!is_null($time))
{
$stat = self::url_stat($path,0);
}
}
else
{
return false;
}
}
$stmt = self::$pdo->prepare('UPDATE '.self::TABLE.' SET fs_modified=:fs_modified WHERE fs_id=:fs_id');
return $stmt->execute(array(
@ -646,9 +667,130 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
));
}
/**
* Chown command, not yet a stream-wrapper function, but necessary
*
* @param string $url
* @param int $owner
* @return boolean
*/
static function chown($url,$owner)
{
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$owner)");
$path = parse_url($url,PHP_URL_PATH);
if (!($stat = self::url_stat($path,0)))
{
if (self::LOG_LEVEL) error_log(__METHOD__."($url,$owner) no such file or directory!");
trigger_error("No such file or directory $url !",E_USER_WARNING);
return false;
}
if (!egw_vfs::$is_root)
{
if (self::LOG_LEVEL) error_log(__METHOD__."($url,$owner) only root can do that!");
trigger_error("Only root can do that!",E_USER_WARNING);
return false;
}
if ($owner < 0 || !$GLOBALS['egw']->accounts->id2name($owner)) // not a user
{
if (self::LOG_LEVEL) error_log(__METHOD__."($url,$owner) unknown (numeric) user id!");
trigger_error("Unknown (numeric) user id!",E_USER_WARNING);
return false;
}
$stmt = self::$pdo->prepare('UPDATE '.self::TABLE.' SET fs_uid=:fs_uid WHERE fs_id=:fs_id');
return $stmt->execute(array(
':fs_uid' => (int) $owner,
':fs_id' => $stat['ino'],
));
}
/**
* Chgrp command, not yet a stream-wrapper function, but necessary
*
* @param string $url
* @param int $group
* @return boolean
*/
static function chgrp($url,$owner)
{
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$owner)");
$path = parse_url($url,PHP_URL_PATH);
if (!($stat = self::url_stat($path,0)))
{
if (self::LOG_LEVEL) error_log(__METHOD__."($url,$owner) no such file or directory!");
trigger_error("No such file or directory $url !",E_USER_WARNING);
return false;
}
if (!egw_vfs::$is_root && $stat['uid'] != egw_vfs::$user)
{
if (self::LOG_LEVEL) error_log(__METHOD__."($url,$owner) only owner or root can do that!");
trigger_error("Only owner or root can do that!",E_USER_WARNING);
return false;
}
if ($owner < 0) $owner = -$owner; // sqlfs uses a positiv group id's!
if ($owner && !$GLOBALS['egw']->accounts->id2name(-$owner)) // not a group
{
if (self::LOG_LEVEL) error_log(__METHOD__."($url,$owner) unknown (numeric) group id!");
trigger_error("Unknown (numeric) group id!",E_USER_WARNING);
return false;
}
$stmt = self::$pdo->prepare('UPDATE '.self::TABLE.' SET fs_gid=:fs_gid WHERE fs_id=:fs_id');
return $stmt->execute(array(
':fs_gid' => $owner,
':fs_id' => $stat['ino'],
));
}
/**
* Chmod command, not yet a stream-wrapper function, but necessary
*
* @param string $url
* @param int $mode
* @return boolean
*/
static function chmod($url,$mode)
{
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$owner)");
$path = parse_url($url,PHP_URL_PATH);
if (!($stat = self::url_stat($path,0)))
{
if (self::LOG_LEVEL) error_log(__METHOD__."($url,$owner) no such file or directory!");
trigger_error("No such file or directory $url !",E_USER_WARNING);
return false;
}
if (!egw_vfs::$is_root && $stat['uid'] != egw_vfs::$user)
{
if (self::LOG_LEVEL) error_log(__METHOD__."($url,$owner) only owner or root can do that!");
trigger_error("Only owner or root can do that!",E_USER_WARNING);
return false;
}
if (!is_numeric($mode)) // not a mode
{
if (self::LOG_LEVEL) error_log(__METHOD__."($url,$owner) no (numeric) mode!");
trigger_error("No (numeric) mode!",E_USER_WARNING);
return false;
}
$stmt = self::$pdo->prepare('UPDATE '.self::TABLE.' SET fs_mode=:fs_mode WHERE fs_id=:fs_id');
return $stmt->execute(array(
':fs_mode' => ((int) $mode) & 0777, // we dont store the file and dir bits, give int overflow!
':fs_id' => $stat['ino'],
));
}
/**
* This method is called immediately when your stream object is created for examining directory contents with opendir().
*
* @ToDo check all parent dirs for readable (fastest would be with sql query) !!!
* @param string $path URL that was passed to opendir() and that this object is expected to explore.
* @param $options
* @return booelan
@ -663,7 +805,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
if (!($stat = self::url_stat($url,0)) || // dir not found
$stat['mime'] != self::DIR_MIME_TYPE || // no dir
!egw_vfs::check_access($stat,egw_vfs::EXECUTABLE)) // no access
!egw_vfs::check_access($stat,egw_vfs::EXECUTABLE|egw_vfs::READABLE)) // no access
{
self::_remove_password($url);
$msg = $stat['mime'] != self::DIR_MIME_TYPE ? "$url is no directory" : 'permission denied';
@ -672,7 +814,13 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
return false;
}
self::$stat_cache = $this->opened_dir = array();
$stmt = self::$pdo->prepare('SELECT fs_id,fs_name,fs_mode,fs_uid,fs_gid,fs_size,fs_mime,fs_created,fs_modified FROM '.self::TABLE.' WHERE fs_dir=?');
$query = 'SELECT fs_id,fs_name,fs_mode,fs_uid,fs_gid,fs_size,fs_mime,fs_created,fs_modified FROM '.self::TABLE.' WHERE fs_dir=?';
// only return readable files, if dir is not writable by user
if (!egw_vfs::check_access($stat,egw_vfs::WRITABLE))
{
$query .= ' AND '.self::_sql_readable();
}
$stmt = self::$pdo->prepare($query);
$stmt->setFetchMode(PDO::FETCH_ASSOC);
if ($stmt->execute(array($stat['ino'])))
{
@ -741,11 +889,17 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
{
if ($n == 0)
{
$query = 1; // / always has fs_id == 1, no need to query it
$query = (int) ($path != '/'); // / always has fs_id == 1, no need to query it ($path=='/' needs fs_dir=0!)
}
elseif ($n < count($parts)-1)
{
$query = 'SELECT fs_id FROM '.self::TABLE.' WHERE fs_dir=('.$query.') AND fs_name='.self::$pdo->quote($name);
// if we are not root, we need to make sure the user has the right to tranverse all partent directories (read-rights)
if (!egw_vfs::$is_root)
{
$query .= ' AND '.$sql_read_acl;
}
}
else
{
@ -757,7 +911,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
if (!($result = self::$pdo->query($query)) || !($info = $result->fetch(PDO::FETCH_ASSOC)))
{
self::_remove_password($url);
if (self::LOG_LEVEL) error_log(__METHOD__."('$url',$flags) file or directory not found!");
if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$url',$flags) file or directory not found!");
return false;
}
self::$stat_cache[$path] = $info;
@ -765,6 +919,27 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
return self::_vfsinfo2stat($info);
}
/**
* Return readable check as sql (to be AND'ed into the query), only use if !egw_vfs::$is_root
*
* @return string
*/
private function _sql_readable()
{
static $sql_read_acl;
if (is_null($sql_read_acl))
{
foreach($GLOBALS['egw']->accounts->memberships(egw_vfs::$user,true) as $gid)
{
$memberships[] = abs($gid); // sqlfs stores the gid's positiv
}
$sql_read_acl = '(fs_mode & 04 OR fs_mode & 0400 AND fs_uid='.(int)egw_vfs::$user.
' OR fs_mode & 040 AND fs_gid IN('.implode(',',$memberships).'))';
}
return $sql_read_acl;
}
/**
* This method is called in response to readdir().
*

View File

@ -63,6 +63,18 @@ class vfs_stream_wrapper implements iface_stream_wrapper
* @var ressource
*/
private $opened_dir;
/**
* URL of the opened dir, used to build the complete URL of files in the dir
*
* @var string
*/
private $opened_dir_url;
/**
* Flag if opened dir is writable, in which case we return un-readable entries too
*
* @var boolean
*/
private $opened_dir_writable;
/**
* Extra dirs from our fstab in the current opened dir
*
@ -357,28 +369,93 @@ class vfs_stream_wrapper implements iface_stream_wrapper
}
/**
* This is not (yet) a stream-wrapper function, but it's necessary and can be used static
* Allow to call methods of the underlying stream wrapper: touch, chmod, chgrp, chown, ...
*
* @param string $path
* @param int $time=null modification time (unix timestamp), default null = current time
* @param int $atime=null access time (unix timestamp), default null = current time, not implemented in the vfs!
* We cant use a magic __call() method, as it does not work for static methods!
*
* @param string $name
* @param array $params first param has to be the path, otherwise we can not determine the correct wrapper
*/
static function touch($path,$time=null,$atime=null)
static private function _call_on_backend($name,$params)
{
if (!($url = self::resolve_url($path)))
$path = $params[0];
if (!($url = self::resolve_url($params[0])))
{
return false;
}
if (($scheme = parse_url($url,PHP_URL_SCHEME)))
{
if (!class_exists($class = $scheme.'_stream_wrapper') || !method_exists($class,'touch'))
if (!class_exists($class = $scheme.'_stream_wrapper') || !method_exists($class,$name))
{
trigger_error("Can't touch for scheme $scheme!\n",E_USER_WARNING);
trigger_error("Can't $name for scheme $scheme!\n",E_USER_WARNING);
return false;
}
return call_user_func(array($scheme.'_stream_wrapper','touch'),$url,$time);
$params[0] = $url;
return call_user_func_array(array($scheme.'_stream_wrapper',$name),$params);
}
return touch($url,$time);
// call the filesystem specific function
if (!function_exists($name))
{
return false;
}
return $name($url,$time);
}
/**
* This is not (yet) a stream-wrapper function, but it's necessary and can be used static
*
* @param string $path
* @param int $time=null modification time (unix timestamp), default null = current time
* @param int $atime=null access time (unix timestamp), default null = current time, not implemented in the vfs!
* @return boolean true on success, false otherwise
*/
static function touch($path,$time=null,$atime=null)
{
return self::_call_on_backend('touch',array($path,$time,$atime));
}
/**
* This is not (yet) a stream-wrapper function, but it's necessary and can be used static
*
* Requires owner or root rights!
*
* @param string $path
* @param string $mode mode string see egw_vfs::mode2int
* @return boolean true on success, false otherwise
*/
static function chmod($path,$mode)
{
return self::_call_on_backend('chmod',array($path,$mode));
}
/**
* This is not (yet) a stream-wrapper function, but it's necessary and can be used static
*
* Requires root rights!
*
* @param string $path
* @param int $owner numeric user id
* @return boolean true on success, false otherwise
*/
static function chown($path,$owner)
{
return self::_call_on_backend('chown',array($path,$owner));
}
/**
* This is not (yet) a stream-wrapper function, but it's necessary and can be used static
*
* Requires owner or root rights!
*
* @param string $path
* @param int $group numeric group id
* @return boolean true on success, false otherwise
*/
static function chgrp($path,$group)
{
return self::_call_on_backend('chgrp',array($path,$group));
}
/**
@ -446,16 +523,18 @@ class vfs_stream_wrapper implements iface_stream_wrapper
$this->opened_dir = $this->extra_dirs = null;
$this->extra_dir_ptr = 0;
if (!($url = self::resolve_url($path)))
if (!($this->opened_dir_url = self::resolve_url($path)))
{
return false;
}
if (!($this->opened_dir = opendir($url)))
if (!($this->opened_dir = opendir($this->opened_dir_url)))
{
return false;
}
$this->opened_dir_writable = ($stat = @stat($this->opened_dir_url)) && egw_vfs::check_access($stat,egw_vfs::WRITABLE);
// check our fstab if we need to add some of the mountpoints
$basepath = parse_url($url,PHP_URL_PATH);
$basepath = parse_url($this->opened_dir_url,PHP_URL_PATH);
foreach(self::$fstab as $mounted => $nul)
{
if (dirname($mounted) == $basepath)
@ -500,8 +579,8 @@ class vfs_stream_wrapper implements iface_stream_wrapper
{
return false;
}
error_log(__METHOD__."('$path',$flags) calling stat($url)");
return stat($url);
//error_log(__METHOD__."('$path',$flags) calling stat($url)");
return @stat($url); // suppressed the stat failed warnings
}
/**
@ -509,6 +588,9 @@ class vfs_stream_wrapper implements iface_stream_wrapper
*
* It should return a string representing the next filename in the location opened by dir_opendir().
*
* Unless other filesystem, we only return files readable by the user, if the dir is not writable for him.
* This is done to hide files and dirs not accessible by the user (eg. other peoples home-dirs in /home).
*
* @return string
*/
function dir_readdir ( )
@ -517,7 +599,18 @@ class vfs_stream_wrapper implements iface_stream_wrapper
{
return $this->extra_dirs[$this->extra_dir_ptr++];
}
return readdir($this->opened_dir);
// only return children readable by the user, if dir is not writable
do {
if (($file = readdir($this->opened_dir)) !== false && !$this->opened_dir_writable)
{
$stat = stat($this->opened_dir_url.'/'.$file);
}
//echo __METHOD__."() opened_dir_writable=$this->opened_dir_writable, file=$file, readable=".(int)egw_vfs::check_access($stat,egw_vfs::READABLE).", loop=".
// (int)($file !== false && $stat && !egw_vfs::check_access($stat,egw_vfs::READABLE))."\n";
}
while($file !== false && $stat && !egw_vfs::check_access($stat,egw_vfs::READABLE));
return $file;
}
/**