2012-07-11 20:51:27 +04:00
< ? php
/**
* ownCloud
*
* @ author Sam Tuke , Frank Karlitschek
2013-05-09 16:43:06 +04:00
* @ copyright 2012 Sam Tuke < samtuke @ owncloud . com > ,
* Frank Karlitschek < frank @ owncloud . org >
2012-07-11 20:51:27 +04:00
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation ; either
* version 3 of the License , or any later version .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details .
*
* You should have received a copy of the GNU Affero General Public
* License along with this library . If not , see < http :// www . gnu . org / licenses />.
*
*/
2013-02-26 22:11:29 +04:00
# Bugs
# ----
# Sharing a file to a user without encryption set up will not provide them with access but won't notify the sharer
2013-04-10 19:37:03 +04:00
# Sharing all files to admin for recovery purposes still in progress
# Possibly public links are broken (not tested since last merge of master)
2013-02-26 22:11:29 +04:00
# Missing features
# ----------------
# Make sure user knows if large files weren't encrypted
2013-03-20 22:26:59 +04:00
# Test
# ----
# Test that writing files works when recovery is enabled, and sharing API is disabled
# Test trashbin support
2013-02-26 22:11:29 +04:00
// Old Todo:
2012-07-11 20:51:27 +04:00
// - Crypt/decrypt button in the userinterface
// - Setting if crypto should be on by default
2013-01-24 22:37:34 +04:00
// - Add a setting "Don´ t encrypt files larger than xx because of performance
// reasons"
2012-07-11 20:51:27 +04:00
2012-10-17 19:35:19 +04:00
namespace OCA\Encryption ;
2012-07-11 20:51:27 +04:00
/**
2012-07-25 18:33:25 +04:00
* @ brief Class for utilities relating to encrypted file storage system
2013-01-31 23:40:51 +04:00
* @ param OC_FilesystemView $view expected to have OC '/' as root path
* @ param string $userId ID of the logged in user
* @ param int $client indicating status of client side encryption . Currently
2012-07-25 18:33:25 +04:00
* unused , likely to become obsolete shortly
2012-07-11 20:51:27 +04:00
*/
class Util {
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
// Web UI:
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
//// DONE: files created via web ui are encrypted
//// DONE: file created & encrypted via web ui are readable in web ui
//// DONE: file created & encrypted via web ui are readable via webdav
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
// WebDAV:
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
//// DONE: new data filled files added via webdav get encrypted
//// DONE: new data filled files added via webdav are readable via webdav
//// DONE: reading unencrypted files when encryption is enabled works via
//// webdav
//// DONE: files created & encrypted via web ui are readable via webdav
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
// Legacy support:
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
//// DONE: add method to check if file is encrypted using new system
//// DONE: add method to check if file is encrypted using old system
//// DONE: add method to fetch legacy key
//// DONE: add method to decrypt legacy encrypted data
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
// Admin UI:
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
//// DONE: changing user password also changes encryption passphrase
2012-12-11 21:12:46 +04:00
2013-01-24 22:37:34 +04:00
//// TODO: add support for optional recovery in case of lost passphrase / keys
//// TODO: add admin optional required long passphrase for users
//// TODO: implement flag system to allow user to specify encryption by folder, subfolder, etc.
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
// Integration testing:
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
//// TODO: test new encryption with versioning
//// TODO: test new encryption with sharing
//// TODO: test new encryption with proxies
2012-12-04 23:53:13 +04:00
2012-11-22 18:08:19 +04:00
2012-07-11 20:51:27 +04:00
private $view ; // OC_FilesystemView object for filesystem operations
2013-01-23 23:24:26 +04:00
private $userId ; // ID of the currently logged-in user
2012-07-11 20:51:27 +04:00
private $pwd ; // User Password
private $client ; // Client side encryption mode flag
2013-01-14 19:39:04 +04:00
private $publicKeyDir ; // Dir containing all public user keys
private $encryptionDir ; // Dir containing user's files_encryption
private $keyfilesPath ; // Dir containing user's keyfiles
private $shareKeysPath ; // Dir containing env keys for shared files
2012-11-16 22:31:37 +04:00
private $publicKeyPath ; // Path to user's public key
private $privateKeyPath ; // Path to user's private key
2012-07-11 20:51:27 +04:00
2012-07-25 18:33:25 +04:00
public function __construct ( \OC_FilesystemView $view , $userId , $client = false ) {
2012-07-11 20:51:27 +04:00
$this -> view = $view ;
2012-07-25 18:33:25 +04:00
$this -> userId = $userId ;
2012-07-11 20:51:27 +04:00
$this -> client = $client ;
2013-01-23 23:24:26 +04:00
$this -> userDir = '/' . $this -> userId ;
2013-04-10 19:37:03 +04:00
$this -> fileFolderName = 'files' ;
$this -> userFilesDir = '/' . $this -> userId . '/' . $this -> fileFolderName ; // TODO: Does this need to be user configurable?
2012-07-25 18:33:25 +04:00
$this -> publicKeyDir = '/' . 'public-keys' ;
$this -> encryptionDir = '/' . $this -> userId . '/' . 'files_encryption' ;
$this -> keyfilesPath = $this -> encryptionDir . '/' . 'keyfiles' ;
2013-01-14 19:39:04 +04:00
$this -> shareKeysPath = $this -> encryptionDir . '/' . 'share-keys' ;
2012-07-25 18:33:25 +04:00
$this -> publicKeyPath = $this -> publicKeyDir . '/' . $this -> userId . '.public.key' ; // e.g. data/public-keys/admin.public.key
$this -> privateKeyPath = $this -> encryptionDir . '/' . $this -> userId . '.private.key' ; // e.g. data/admin/admin.private.key
2012-07-11 20:51:27 +04:00
}
public function ready () {
if (
2013-05-09 16:43:06 +04:00
! $this -> view -> file_exists ( $this -> encryptionDir )
or ! $this -> view -> file_exists ( $this -> keyfilesPath )
or ! $this -> view -> file_exists ( $this -> shareKeysPath )
or ! $this -> view -> file_exists ( $this -> publicKeyPath )
or ! $this -> view -> file_exists ( $this -> privateKeyPath )
2012-07-11 20:51:27 +04:00
) {
return false ;
} else {
2013-05-08 18:22:08 +04:00
2012-07-11 20:51:27 +04:00
return true ;
}
}
2012-07-24 20:53:12 +04:00
/**
2012-07-25 18:33:25 +04:00
* @ brief Sets up user folders and keys for serverside encryption
2012-07-24 20:53:12 +04:00
* @ param $passphrase passphrase to encrypt server - stored private key with
*/
2012-07-25 18:33:25 +04:00
public function setupServerSide ( $passphrase = null ) {
2012-07-11 20:51:27 +04:00
2013-03-19 22:53:15 +04:00
// Set directories to check / create
$setUpDirs = array (
$this -> userDir
, $this -> userFilesDir
, $this -> publicKeyDir
, $this -> encryptionDir
, $this -> keyfilesPath
, $this -> shareKeysPath
);
2013-01-23 23:24:26 +04:00
2013-03-19 22:53:15 +04:00
// Check / create all necessary dirs
foreach ( $setUpDirs as $dirPath ) {
2013-01-23 23:24:26 +04:00
2013-03-19 22:53:15 +04:00
if ( ! $this -> view -> file_exists ( $dirPath ) ) {
$this -> view -> mkdir ( $dirPath );
}
2013-01-14 19:39:04 +04:00
}
2012-07-11 20:51:27 +04:00
// Create user keypair
if (
2013-01-24 22:37:34 +04:00
! $this -> view -> file_exists ( $this -> publicKeyPath )
or ! $this -> view -> file_exists ( $this -> privateKeyPath )
2012-07-11 20:51:27 +04:00
) {
// Generate keypair
$keypair = Crypt :: createKeypair ();
2013-01-23 23:24:26 +04:00
2012-07-25 18:33:25 +04:00
\OC_FileProxy :: $enabled = false ;
2012-07-11 20:51:27 +04:00
// Save public key
2012-07-25 18:33:25 +04:00
$this -> view -> file_put_contents ( $this -> publicKeyPath , $keypair [ 'publicKey' ] );
2012-07-11 20:51:27 +04:00
2012-07-25 18:33:25 +04:00
// Encrypt private key with user pwd as passphrase
$encryptedPrivateKey = Crypt :: symmetricEncryptFileContent ( $keypair [ 'privateKey' ], $passphrase );
2012-07-11 20:51:27 +04:00
2012-07-25 18:33:25 +04:00
// Save private key
$this -> view -> file_put_contents ( $this -> privateKeyPath , $encryptedPrivateKey );
2012-07-11 20:51:27 +04:00
2012-07-25 18:33:25 +04:00
\OC_FileProxy :: $enabled = true ;
2013-05-01 21:18:31 +04:00
}
// If there's no record for this user's encryption preferences
if ( false === $this -> recoveryEnabledForUser () ) {
// create database configuration
$sql = 'INSERT INTO `*PREFIX*encryption` (`uid`,`mode`,`recovery`) VALUES (?,?,?)' ;
$args = array ( $this -> userId , 'server-side' , 0 );
$query = \OCP\DB :: prepare ( $sql );
$query -> execute ( $args );
2012-07-11 20:51:27 +04:00
}
2012-11-16 22:31:37 +04:00
return true ;
2012-07-11 20:51:27 +04:00
}
2012-07-24 20:53:12 +04:00
2013-03-20 22:26:59 +04:00
/**
* @ brief Check whether pwd recovery is enabled for a given user
2013-05-01 21:18:31 +04:00
* @ return 1 = yes , 0 = no , false = no record
2013-03-20 22:26:59 +04:00
* @ note If records are not being returned , check for a hidden space
* at the start of the uid in db
*/
2013-05-01 21:18:31 +04:00
public function recoveryEnabledForUser () {
2013-03-19 22:53:15 +04:00
2013-03-20 22:26:59 +04:00
$sql = ' SELECT
recovery
FROM
`*PREFIX*encryption`
WHERE
uid = ? ' ;
$args = array ( $this -> userId );
2013-03-19 22:53:15 +04:00
2013-03-20 22:26:59 +04:00
$query = \OCP\DB :: prepare ( $sql );
$result = $query -> execute ( $args );
2013-05-01 21:18:31 +04:00
$recoveryEnabled = array ();
2013-03-20 22:26:59 +04:00
while ( $row = $result -> fetchRow () ) {
2013-05-01 21:18:31 +04:00
$recoveryEnabled [] = $row [ 'recovery' ];
2013-03-20 22:26:59 +04:00
}
2013-05-01 21:18:31 +04:00
// If no record is found
if ( empty ( $recoveryEnabled ) ) {
return false ;
// If a record is found
} else {
return $recoveryEnabled [ 0 ];
}
2013-03-20 22:26:59 +04:00
}
2013-03-19 22:53:15 +04:00
2013-03-20 22:26:59 +04:00
/**
* @ brief Enable / disable pwd recovery for a given user
* @ param bool $enabled Whether to enable or disable recovery
* @ return bool
*/
2013-05-01 21:18:31 +04:00
public function setRecoveryForUser ( $enabled ) {
2013-03-20 22:26:59 +04:00
2013-05-01 21:18:31 +04:00
$recoveryStatus = $this -> recoveryEnabledForUser ();
// If a record for this user already exists, update it
if ( false === $recoveryStatus ) {
2013-03-20 22:26:59 +04:00
2013-05-01 21:18:31 +04:00
$sql = ' INSERT INTO `*PREFIX*encryption`
( `uid` , `mode` , `recovery` )
VALUES ( ? , ? , ? ) ' ;
$args = array ( $this -> userId , 'server-side' , $enabled );
2013-03-20 22:26:59 +04:00
2013-05-01 21:18:31 +04:00
// Create a new record instead
} else {
$sql = ' UPDATE
* PREFIX * encryption
SET
recovery = ?
WHERE
uid = ? ' ;
$args = array ( $enabled , $this -> userId );
}
2013-03-20 22:26:59 +04:00
$query = \OCP\DB :: prepare ( $sql );
if ( $query -> execute ( $args ) ) {
return true ;
} else {
return false ;
}
2013-03-19 22:53:15 +04:00
}
2013-01-23 23:24:26 +04:00
/**
* @ brief Find all files and their encryption status within a directory
* @ param string $directory The path of the parent directory to search
* @ return mixed false if 0 found , array on success . Keys : name , path
2013-01-29 23:54:40 +04:00
* @ note $directory needs to be a path relative to OC data dir . e . g .
* / admin / files NOT / backup OR / home / www / oc / data / admin / files
2013-01-23 23:24:26 +04:00
*/
2013-02-20 23:18:00 +04:00
public function findEncFiles ( $directory ) {
2013-01-23 23:24:26 +04:00
// Disable proxy - we don't want files to be decrypted before
// we handle them
\OC_FileProxy :: $enabled = false ;
$found = array ( 'plain' => array (), 'encrypted' => array (), 'legacy' => array () );
if (
2013-01-24 22:37:34 +04:00
$this -> view -> is_dir ( $directory )
&& $handle = $this -> view -> opendir ( $directory )
2013-01-23 23:24:26 +04:00
) {
2012-07-31 22:28:11 +04:00
while ( false !== ( $file = readdir ( $handle ) ) ) {
2013-01-23 23:24:26 +04:00
2012-07-31 22:28:11 +04:00
if (
$file != " . "
&& $file != " .. "
) {
2013-01-23 23:24:26 +04:00
$filePath = $directory . '/' . $this -> view -> getRelativePath ( '/' . $file );
2013-02-05 19:59:28 +04:00
$relPath = $this -> stripUserFilesPath ( $filePath );
2012-07-31 22:28:11 +04:00
2013-01-23 23:24:26 +04:00
// If the path is a directory, search
// its contents
2012-07-31 22:28:11 +04:00
if ( $this -> view -> is_dir ( $filePath ) ) {
2013-02-20 23:18:00 +04:00
$this -> findEncFiles ( $filePath );
2012-07-31 22:28:11 +04:00
2013-01-23 23:24:26 +04:00
// If the path is a file, determine
// its encryption status
} elseif ( $this -> view -> is_file ( $filePath ) ) {
2012-07-31 22:28:11 +04:00
2013-01-23 23:24:26 +04:00
// Disable proxies again, some-
2013-01-31 23:40:51 +04:00
// where they got re-enabled :/
2013-01-23 23:24:26 +04:00
\OC_FileProxy :: $enabled = false ;
2012-07-31 22:28:11 +04:00
2013-01-31 23:40:51 +04:00
$data = $this -> view -> file_get_contents ( $filePath );
2013-01-23 23:24:26 +04:00
// If the file is encrypted
2013-01-31 23:40:51 +04:00
// NOTE: If the userId is
// empty or not set, file will
// detected as plain
2013-02-01 23:31:15 +04:00
// NOTE: This is inefficient;
// scanning every file like this
// will eat server resources :(
2013-01-31 23:40:51 +04:00
if (
2013-04-22 20:50:59 +04:00
Keymanager :: getFileKey ( $this -> view , $this -> userId , $relPath )
2013-03-09 22:18:34 +04:00
&& Crypt :: isCatfileContent ( $data )
2013-01-31 23:40:51 +04:00
) {
2012-07-31 22:28:11 +04:00
2013-01-23 23:24:26 +04:00
$found [ 'encrypted' ][] = array ( 'name' => $file , 'path' => $filePath );
// If the file uses old
// encryption system
2013-03-09 22:18:34 +04:00
} elseif ( Crypt :: isLegacyEncryptedContent ( $this -> tail ( $filePath , 3 ), $relPath ) ) {
2012-07-31 22:28:11 +04:00
2013-01-23 23:24:26 +04:00
$found [ 'legacy' ][] = array ( 'name' => $file , 'path' => $filePath );
2012-07-31 22:28:11 +04:00
2013-01-23 23:24:26 +04:00
// If the file is not encrypted
} else {
2013-04-12 16:30:02 +04:00
$found [ 'plain' ][] = array ( 'name' => $file , 'path' => $relPath );
2012-07-31 22:28:11 +04:00
}
}
}
}
2013-01-23 23:24:26 +04:00
\OC_FileProxy :: $enabled = true ;
2012-07-31 22:28:11 +04:00
2013-01-23 23:24:26 +04:00
if ( empty ( $found ) ) {
return false ;
2012-07-31 22:28:11 +04:00
} else {
2013-01-29 23:54:40 +04:00
2013-01-23 23:24:26 +04:00
return $found ;
2012-07-31 22:28:11 +04:00
}
}
2013-01-23 23:24:26 +04:00
\OC_FileProxy :: $enabled = true ;
2012-07-31 22:28:11 +04:00
return false ;
}
2013-03-09 22:18:34 +04:00
/**
* @ brief Fetch the last lines of a file efficiently
* @ note Safe to use on large files ; does not read entire file to memory
* @ note Derivative of http :// tekkie . flashbit . net / php / tail - functionality - in - php
*/
public function tail ( $filename , $numLines ) {
\OC_FileProxy :: $enabled = false ;
$text = '' ;
$pos = - 1 ;
$handle = $this -> view -> fopen ( $filename , 'r' );
while ( $numLines > 0 ) {
-- $pos ;
if ( fseek ( $handle , $pos , SEEK_END ) !== 0 ) {
rewind ( $handle );
$numLines = 0 ;
} elseif ( fgetc ( $handle ) === " \n " ) {
-- $numLines ;
}
$block_size = ( - $pos ) % 8192 ;
if ( $block_size === 0 || $numLines === 0 ) {
$text = fread ( $handle , ( $block_size === 0 ? 8192 : $block_size ) ) . $text ;
}
}
fclose ( $handle );
\OC_FileProxy :: $enabled = true ;
return $text ;
}
2013-05-09 20:16:59 +04:00
/**
* @ brief Check if a given path identifies an encrypted file
* @ return true / false
*/
2012-10-17 19:35:19 +04:00
public function isEncryptedPath ( $path ) {
2012-12-04 23:53:13 +04:00
// Disable encryption proxy so data retreived is in its
// original form
\OC_FileProxy :: $enabled = false ;
2012-10-17 19:35:19 +04:00
$data = $this -> view -> file_get_contents ( $path );
2012-12-04 23:53:13 +04:00
\OC_FileProxy :: $enabled = true ;
2013-03-09 22:18:34 +04:00
return Crypt :: isCatfileContent ( $data );
2012-10-17 19:35:19 +04:00
}
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
/**
* @ brief get the file size of the unencrypted file
* @ param $path absolute path
* @ return bool
*/
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
public function getFileSize ( $path ) {
$result = 0 ;
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy :: $enabled ;
\OC_FileProxy :: $enabled = false ;
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
// Reformat path for use with OC_FSV
$pathSplit = explode ( '/' , $path );
$pathRelative = implode ( '/' , array_slice ( $pathSplit , 3 ) );
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
if ( $pathSplit [ 2 ] == 'files' && $this -> view -> file_exists ( $path ) && $this -> isEncryptedPath ( $path )) {
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
// get the size from filesystem
$fullPath = $this -> view -> getLocalFile ( $path );
$size = filesize ( $fullPath );
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
// calculate last chunk nr
$lastChunckNr = floor ( $size / 8192 );
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
// open stream
$stream = fopen ( 'crypt://' . $pathRelative , " r " );
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
if ( is_resource ( $stream )) {
// calculate last chunk position
$lastChunckPos = ( $lastChunckNr * 8192 );
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
// seek to end
fseek ( $stream , $lastChunckPos );
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
// get the content of the last chunk
$lastChunkContent = fread ( $stream , 8192 );
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
// calc the real file size with the size of the last chunk
$realSize = (( $lastChunckNr * 6126 ) + strlen ( $lastChunkContent ));
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
// store file size
$result = $realSize ;
}
}
2013-04-28 01:34:25 +04:00
2013-05-09 20:16:59 +04:00
\OC_FileProxy :: $enabled = $proxyStatus ;
2013-04-28 01:34:25 +04:00
2013-05-09 20:16:59 +04:00
return $result ;
}
/**
* @ brief fix the file size of the encrypted file
* @ param $path absolute path
* @ return true / false if file is encrypted
*/
2013-04-28 01:34:25 +04:00
2013-05-09 20:16:59 +04:00
public function fixFileSize ( $path ) {
$result = false ;
2013-04-28 01:34:25 +04:00
2013-05-09 20:16:59 +04:00
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy :: $enabled ;
\OC_FileProxy :: $enabled = false ;
2013-04-28 01:34:25 +04:00
2013-05-09 20:16:59 +04:00
$realSize = $this -> getFileSize ( $path );
if ( $realSize > 0 ) {
$cached = $this -> view -> getFileInfo ( $path );
$cached [ 'encrypted' ] = 1 ;
2013-04-28 01:34:25 +04:00
2013-05-09 20:16:59 +04:00
// set the size
$cached [ 'unencrypted_size' ] = $realSize ;
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
// put file info
$this -> view -> putFileInfo ( $path , $cached );
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
$result = true ;
}
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
\OC_FileProxy :: $enabled = $proxyStatus ;
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
return $result ;
}
2013-04-27 02:05:20 +04:00
2013-02-05 19:59:28 +04:00
/**
* @ brief Format a path to be relative to the / user / files / directory
2013-05-09 20:09:20 +04:00
* @ note e . g . turns '/admin/files/test.txt' into 'test.txt'
2013-02-05 19:59:28 +04:00
*/
public function stripUserFilesPath ( $path ) {
$trimmed = ltrim ( $path , '/' );
$split = explode ( '/' , $trimmed );
$sliced = array_slice ( $split , 2 );
$relPath = implode ( '/' , $sliced );
return $relPath ;
}
2013-05-09 20:09:20 +04:00
/**
* @ brief Format a path to be relative to the / user directory
* @ note e . g . turns '/admin/files/test.txt' into 'files/test.txt'
*/
public function stripFilesPath ( $path ) {
$trimmed = ltrim ( $path , '/' );
$split = explode ( '/' , $trimmed );
$sliced = array_slice ( $split , 1 );
$relPath = implode ( '/' , $sliced );
return $relPath ;
}
2013-02-20 23:18:00 +04:00
/**
* @ brief Format a shared path to be relative to the / user / files / directory
* @ note Expects a path like / uid / files / Shared / filepath
*/
public function stripSharedFilePath ( $path ) {
$trimmed = ltrim ( $path , '/' );
$split = explode ( '/' , $trimmed );
$sliced = array_slice ( $split , 3 );
$relPath = implode ( '/' , $sliced );
return $relPath ;
}
public function isSharedPath ( $path ) {
$trimmed = ltrim ( $path , '/' );
$split = explode ( '/' , $trimmed );
if ( $split [ 2 ] == " Shared " ) {
return true ;
} else {
return false ;
}
}
2013-01-23 23:24:26 +04:00
/**
* @ brief Encrypt all files in a directory
* @ param string $publicKey the public key to encrypt files with
* @ param string $dirPath the directory whose files will be encrypted
* @ note Encryption is recursive
*/
2013-01-24 22:37:34 +04:00
public function encryptAll ( $publicKey , $dirPath , $legacyPassphrase = null , $newPassphrase = null ) {
2013-04-23 19:36:35 +04:00
2013-02-20 23:18:00 +04:00
if ( $found = $this -> findEncFiles ( $dirPath ) ) {
2012-07-31 22:28:11 +04:00
2013-01-29 23:54:40 +04:00
// Disable proxy to prevent file being encrypted twice
\OC_FileProxy :: $enabled = false ;
2013-01-23 23:24:26 +04:00
// Encrypt unencrypted files
2013-01-29 23:54:40 +04:00
foreach ( $found [ 'plain' ] as $plainFile ) {
2013-01-23 23:24:26 +04:00
2013-04-12 17:18:19 +04:00
//relative to data/<user>/file
$relPath = $plainFile [ 'path' ];
2013-04-23 20:41:01 +04:00
2013-04-12 17:18:19 +04:00
//relative to /data
$rawPath = $this -> userId . '/files/' . $plainFile [ 'path' ];
2013-01-23 23:24:26 +04:00
2013-04-23 20:41:01 +04:00
// Open plain file handle for binary reading
$plainHandle1 = $this -> view -> fopen ( $rawPath , 'rb' );
// 2nd handle for moving plain file - view->rename() doesn't work, this is a workaround
$plainHandle2 = $this -> view -> fopen ( $rawPath . '.plaintmp' , 'wb' );
// Move plain file to a temporary location
stream_copy_to_stream ( $plainHandle1 , $plainHandle2 );
// Close access to original file
2013-05-09 20:16:59 +04:00
// $this->view->fclose( $plainHandle1 ); // not implemented in view{}
2013-04-23 20:41:01 +04:00
// Delete original plain file so we can rename enc file later
$this -> view -> unlink ( $rawPath );
// Open enc file handle for binary writing, with same filename as original plain file
$encHandle = fopen ( 'crypt://' . $relPath , 'wb' );
// Save data from plain stream to new encrypted file via enc stream
// NOTE: Stream{} will be invoked for handling
// the encryption, and should handle all keys
// and their generation etc. automatically
$size = stream_copy_to_stream ( $plainHandle2 , $encHandle );
2013-01-31 23:40:51 +04:00
2013-04-23 20:41:01 +04:00
// Delete temporary plain copy of file
$this -> view -> unlink ( $rawPath . '.plaintmp' );
2013-01-31 23:40:51 +04:00
// Add the file to the cache
\OC\Files\Filesystem :: putFileInfo ( $plainFile [ 'path' ], array ( 'encrypted' => true , 'size' => $size ), '' );
2013-01-23 23:24:26 +04:00
}
2012-07-31 22:28:11 +04:00
2013-01-23 23:24:26 +04:00
// Encrypt legacy encrypted files
2013-01-24 22:37:34 +04:00
if (
! empty ( $legacyPassphrase )
&& ! empty ( $newPassphrase )
) {
2013-01-23 23:24:26 +04:00
2013-02-05 19:35:29 +04:00
foreach ( $found [ 'legacy' ] as $legacyFile ) {
2013-01-23 23:24:26 +04:00
2013-01-24 22:37:34 +04:00
// Fetch data from file
2013-02-05 19:35:29 +04:00
$legacyData = $this -> view -> file_get_contents ( $legacyFile [ 'path' ] );
2013-01-24 22:37:34 +04:00
// Recrypt data, generate catfile
$recrypted = Crypt :: legacyKeyRecryptKeyfile ( $legacyData , $legacyPassphrase , $publicKey , $newPassphrase );
2013-04-12 17:18:19 +04:00
$relPath = $legacyFile [ 'path' ];
$rawPath = $this -> userId . '/files/' . $plainFile [ 'path' ];
2013-02-05 19:35:29 +04:00
2013-01-31 23:40:51 +04:00
// Save keyfile
2013-02-05 19:35:29 +04:00
Keymanager :: setFileKey ( $this -> view , $relPath , $this -> userId , $recrypted [ 'key' ] );
2013-01-24 22:37:34 +04:00
// Overwrite the existing file with the encrypted one
2013-04-12 17:18:19 +04:00
$this -> view -> file_put_contents ( $rawPath , $recrypted [ 'data' ] );
2013-01-31 23:40:51 +04:00
$size = strlen ( $recrypted [ 'data' ] );
// Add the file to the cache
2013-04-12 17:18:19 +04:00
\OC\Files\Filesystem :: putFileInfo ( $rawPath , array ( 'encrypted' => true , 'size' => $size ), '' );
2013-01-24 22:37:34 +04:00
}
2013-01-23 23:24:26 +04:00
}
2013-01-29 23:54:40 +04:00
\OC_FileProxy :: $enabled = true ;
// If files were found, return true
return true ;
2013-01-23 23:24:26 +04:00
2013-01-29 23:54:40 +04:00
} else {
// If no files were found, return false
return false ;
2012-07-31 22:28:11 +04:00
}
}
2013-02-06 18:30:40 +04:00
/**
* @ brief Return important encryption related paths
* @ param string $pathName Name of the directory to return the path of
* @ return string path
*/
2012-11-16 22:31:37 +04:00
public function getPath ( $pathName ) {
switch ( $pathName ) {
case 'publicKeyDir' :
return $this -> publicKeyDir ;
break ;
case 'encryptionDir' :
return $this -> encryptionDir ;
break ;
case 'keyfilesPath' :
return $this -> keyfilesPath ;
break ;
case 'publicKeyPath' :
return $this -> publicKeyPath ;
break ;
case 'privateKeyPath' :
return $this -> privateKeyPath ;
break ;
}
}
2013-02-12 19:48:04 +04:00
/**
* @ brief get path of a file .
* @ param $fileId id of the file
* @ return path of the file
*/
2013-02-20 23:18:00 +04:00
public static function fileIdToPath ( $fileId ) {
$query = \OC_DB :: prepare ( 'SELECT `path`'
2013-02-12 19:48:04 +04:00
. ' FROM `*PREFIX*filecache`'
2013-02-20 23:18:00 +04:00
. ' WHERE `fileid` = ?' );
$result = $query -> execute ( array ( $fileId ) );
2013-02-12 19:48:04 +04:00
$row = $result -> fetchRow ();
2013-02-20 23:18:00 +04:00
return substr ( $row [ 'path' ], 5 );
2013-02-12 19:48:04 +04:00
}
2013-02-19 23:16:50 +04:00
/**
* @ brief Filter an array of UIDs to return only ones ready for sharing
* @ param array $unfilteredUsers users to be checked for sharing readiness
2013-03-30 00:11:29 +04:00
* @ return multi - dimensional array . keys : ready , unready
2013-02-19 23:16:50 +04:00
*/
public function filterShareReadyUsers ( $unfilteredUsers ) {
2013-05-09 16:43:06 +04:00
2013-02-19 23:16:50 +04:00
// This array will collect the filtered IDs
2013-04-18 22:02:27 +04:00
$readyIds = $unreadyIds = array ();
2013-02-19 23:16:50 +04:00
// Loop through users and create array of UIDs that need new keyfiles
foreach ( $unfilteredUsers as $user ) {
$util = new Util ( $this -> view , $user );
// Check that the user is encryption capable, or is the
// public system user 'ownCloud' (for public shares)
if (
2013-05-09 16:43:06 +04:00
$user == 'owncloud'
or $util -> ready ()
2013-02-19 23:16:50 +04:00
) {
2013-03-30 00:11:29 +04:00
// Construct array of ready UIDs for Keymanager{}
$readyIds [] = $user ;
2013-02-19 23:16:50 +04:00
} else {
2013-03-30 00:11:29 +04:00
// Construct array of unready UIDs for Keymanager{}
$unreadyIds [] = $user ;
2013-02-19 23:16:50 +04:00
// Log warning; we can't do necessary setup here
// because we don't have the user passphrase
\OC_Log :: write ( 'Encryption library' , '"' . $user . '" is not setup for encryption' , \OC_Log :: WARN );
}
}
2013-03-30 00:11:29 +04:00
return array (
2013-04-18 22:02:27 +04:00
'ready' => $readyIds
2013-03-30 00:11:29 +04:00
, 'unready' => $unreadyIds
);
2013-02-19 23:16:50 +04:00
}
2013-02-20 23:18:00 +04:00
/**
2013-02-27 22:46:44 +04:00
* @ brief Decrypt a keyfile without knowing how it was encrypted
* @ param string $filePath
* @ param string $fileOwner
* @ param string $privateKey
* @ note Checks whether file was encrypted with openssl_seal or
* openssl_encrypt , and decrypts accrdingly
2013-04-10 19:37:03 +04:00
* @ note This was used when 2 types of encryption for keyfiles was used ,
* but now we ' ve switched to exclusively using openssl_seal ()
2013-02-20 23:18:00 +04:00
*/
2013-02-27 22:46:44 +04:00
public function decryptUnknownKeyfile ( $filePath , $fileOwner , $privateKey ) {
2013-02-20 23:18:00 +04:00
// Get the encrypted keyfile
// NOTE: the keyfile format depends on how it was encrypted! At
// this stage we don't know how it was encrypted
$encKeyfile = Keymanager :: getFileKey ( $this -> view , $this -> userId , $filePath );
// We need to decrypt the keyfile
// Has the file been shared yet?
if (
$this -> userId == $fileOwner
&& ! Keymanager :: getShareKey ( $this -> view , $this -> userId , $filePath ) // NOTE: we can't use isShared() here because it's a post share hook so it always returns true
) {
// The file has no shareKey, and its keyfile must be
// decrypted conventionally
$plainKeyfile = Crypt :: keyDecrypt ( $encKeyfile , $privateKey );
} else {
// The file has a shareKey and must use it for decryption
$shareKey = Keymanager :: getShareKey ( $this -> view , $this -> userId , $filePath );
$plainKeyfile = Crypt :: multiKeyDecrypt ( $encKeyfile , $shareKey , $privateKey );
}
2013-02-27 22:46:44 +04:00
return $plainKeyfile ;
}
/**
* @ brief Encrypt keyfile to multiple users
* @ param array $users list of users which should be able to access the file
* @ param string $filePath path of the file to be shared
2013-05-09 16:43:06 +04:00
* @ return bool
2013-02-27 22:46:44 +04:00
*/
public function setSharedFileKeyfiles ( Session $session , array $users , $filePath ) {
2013-05-08 18:22:08 +04:00
2013-02-27 22:46:44 +04:00
// Make sure users are capable of sharing
$filteredUids = $this -> filterShareReadyUsers ( $users );
2013-05-09 16:43:06 +04:00
// If we're attempting to share to unready users
2013-03-30 00:11:29 +04:00
if ( ! empty ( $filteredUids [ 'unready' ] ) ) {
2013-05-09 16:43:06 +04:00
2013-05-08 18:22:08 +04:00
\OC_Log :: write ( 'Encryption library' , 'Sharing to these user(s) failed as they are unready for encryption:"' . print_r ( $filteredUids [ 'unready' ], 1 ), \OC_Log :: WARN );
2013-03-30 00:11:29 +04:00
2013-05-09 16:43:06 +04:00
return false ;
2013-03-30 00:11:29 +04:00
}
2013-02-27 22:46:44 +04:00
// Get public keys for each user, ready for generating sharekeys
2013-04-18 22:02:27 +04:00
$userPubKeys = Keymanager :: getPublicKeys ( $this -> view , $filteredUids [ 'ready' ] );
2013-05-09 16:43:06 +04:00
// Note proxy status then disable it
$proxyStatus = \OC_FileProxy :: $enabled ;
2013-02-27 22:46:44 +04:00
\OC_FileProxy :: $enabled = false ;
// Get the current users's private key for decrypting existing keyfile
$privateKey = $session -> getPrivateKey ();
$fileOwner = \OC\Files\Filesystem :: getOwner ( $filePath );
2013-03-19 22:53:15 +04:00
// Decrypt keyfile
2013-02-27 22:46:44 +04:00
$plainKeyfile = $this -> decryptUnknownKeyfile ( $filePath , $fileOwner , $privateKey );
2013-02-20 23:18:00 +04:00
// Re-enc keyfile to (additional) sharekeys
2013-02-26 22:11:29 +04:00
$multiEncKey = Crypt :: multiKeyEncrypt ( $plainKeyfile , $userPubKeys );
// Save the recrypted key to it's owner's keyfiles directory
// Save new sharekeys to all necessary user directory
if (
! Keymanager :: setFileKey ( $this -> view , $filePath , $fileOwner , $multiEncKey [ 'data' ] )
|| ! Keymanager :: setShareKeys ( $this -> view , $filePath , $multiEncKey [ 'keys' ] )
) {
2013-02-20 23:18:00 +04:00
2013-05-09 16:43:06 +04:00
\OC_Log :: write ( 'Encryption library' , 'Keyfiles could not be saved for users sharing ' . $filePath , \OC_Log :: ERROR );
return false ;
2013-02-20 23:18:00 +04:00
}
2013-05-09 16:43:06 +04:00
// Return proxy to original status
\OC_FileProxy :: $enabled = $proxyStatus ;
2013-02-20 23:18:00 +04:00
return true ;
}
2013-03-30 00:11:29 +04:00
2013-04-16 20:29:22 +04:00
/**
* @ brief Find , sanitise and format users sharing a file
* @ note This wraps other methods into a portable bundle
*/
2013-04-22 14:25:55 +04:00
public function getSharingUsersArray ( $sharingEnabled , $filePath , $currentUserId = false ) {
2012-07-11 20:51:27 +04:00
2013-04-16 20:29:22 +04:00
// Check if key recovery is enabled
2013-05-04 18:14:38 +04:00
if (
\OC_Appconfig :: getValue ( 'files_encryption' , 'recoveryAdminEnabled' )
&& $this -> recoveryEnabledForUser ()
) {
$recoveryEnabled = true ;
} else {
$recoveryEnabled = false ;
}
2013-04-16 20:29:22 +04:00
// Make sure that a share key is generated for the owner too
2013-05-04 18:14:38 +04:00
list ( $owner , $ownerPath ) = $this -> getUidAndFilename ( $filePath );
2013-04-18 19:53:59 +04:00
2013-04-16 20:29:22 +04:00
if ( $sharingEnabled ) {
// Find out who, if anyone, is sharing the file
2013-04-22 16:14:28 +04:00
$userIds = \OCP\Share :: getUsersSharingFile ( $ownerPath , $owner , true , true , true );
2013-04-16 20:29:22 +04:00
}
// If recovery is enabled, add the
// Admin UID to list of users to share to
if ( $recoveryEnabled ) {
2013-05-04 18:14:38 +04:00
// Find recoveryAdmin user ID
$recoveryAdminUid = \OC_Appconfig :: getValue ( 'files_encryption' , 'recoveryAdminUid' );
// Add recoveryAdmin to list of users sharing
$userIds [] = $recoveryAdminUid ;
2013-04-16 20:29:22 +04:00
}
2013-04-22 14:25:55 +04:00
2013-05-04 18:14:38 +04:00
// add current user if given
if ( $currentUserId != false ) {
2013-05-09 16:43:06 +04:00
$userIds [] = $currentUserId ;
2013-05-04 18:14:38 +04:00
}
2013-04-22 14:25:55 +04:00
2013-04-16 20:29:22 +04:00
// Remove duplicate UIDs
$uniqueUserIds = array_unique ( $userIds );
return $uniqueUserIds ;
}
2013-05-04 18:14:38 +04:00
/**
* @ brief Set file migration status for user
2013-05-09 16:43:06 +04:00
* @ return bool
2013-05-04 18:14:38 +04:00
*/
public function setMigrationStatus ( $status ) {
$sql = ' UPDATE
* PREFIX * encryption
SET
migrationStatus = ?
WHERE
uid = ? ' ;
$args = array ( $status , $this -> userId );
$query = \OCP\DB :: prepare ( $sql );
if ( $query -> execute ( $args ) ) {
return true ;
} else {
return false ;
}
}
/**
* @ brief Check whether pwd recovery is enabled for a given user
* @ return 1 = yes , 0 = no , false = no record
* @ note If records are not being returned , check for a hidden space
* at the start of the uid in db
*/
public function getMigrationStatus () {
$sql = ' SELECT
migrationStatus
FROM
`*PREFIX*encryption`
WHERE
uid = ? ' ;
$args = array ( $this -> userId );
$query = \OCP\DB :: prepare ( $sql );
$result = $query -> execute ( $args );
$migrationStatus = array ();
while ( $row = $result -> fetchRow () ) {
$migrationStatus [] = $row [ 'migrationStatus' ];
}
// If no record is found
if ( empty ( $migrationStatus ) ) {
return false ;
// If a record is found
} else {
return $migrationStatus [ 0 ];
}
}
2013-04-16 20:29:22 +04:00
2013-03-30 00:11:29 +04:00
/**
2013-03-26 15:39:55 +04:00
* @ brief get uid of the owners of the file and the path to the file
2013-04-18 18:37:49 +04:00
* @ param $path Path of the file to check
2013-04-10 19:37:03 +04:00
* @ note $shareFilePath must be relative to data / UID / files . Files
* relative to / Shared are also acceptable
2013-03-26 15:39:55 +04:00
* @ return array
*/
2013-04-18 18:37:49 +04:00
public function getUidAndFilename ( $path ) {
2013-05-07 15:42:49 +04:00
$view = new \OC\Files\View ( $this -> userFilesDir );
$fileOwnerUid = $view -> getOwner ( $path );
2013-04-10 19:37:03 +04:00
// Check that UID is valid
if ( ! \OCP\User :: userExists ( $fileOwnerUid ) ) {
2013-04-18 18:37:49 +04:00
throw new \Exception ( 'Could not find owner (UID = "' . var_export ( $fileOwnerUid , 1 ) . '") of file "' . $path . '"' );
2013-04-10 19:37:03 +04:00
}
// NOTE: Bah, this dependency should be elsewhere
\OC\Files\Filesystem :: initMountPoints ( $fileOwnerUid );
// If the file owner is the currently logged in user
if ( $fileOwnerUid == $this -> userId ) {
// Assume the path supplied is correct
2013-04-18 18:37:49 +04:00
$filename = $path ;
2013-04-10 19:37:03 +04:00
} else {
2013-05-07 15:42:49 +04:00
$info = $view -> getFileInfo ( $path );
2013-04-10 19:37:03 +04:00
$ownerView = new \OC\Files\View ( '/' . $fileOwnerUid . '/files' );
// Fetch real file path from DB
$filename = $ownerView -> getPath ( $info [ 'fileid' ] ); // TODO: Check that this returns a path without including the user data dir
2013-03-26 15:39:55 +04:00
}
2013-04-10 19:37:03 +04:00
// Make path relative for use by $view
2013-05-07 15:42:49 +04:00
$relpath = \OC\Files\Filesystem :: normalizePath ( $fileOwnerUid . '/' . $this -> fileFolderName . '/' . $filename );
2013-04-10 19:37:03 +04:00
// Check that the filename we're using is working
if ( $this -> view -> file_exists ( $relpath ) ) {
2013-04-18 15:41:21 +04:00
return array ( $fileOwnerUid , $filename );
2013-04-10 19:37:03 +04:00
} else {
2013-04-25 22:23:54 +04:00
return false ;
2013-04-10 19:37:03 +04:00
}
2013-03-26 15:39:55 +04:00
}
2013-04-22 13:58:39 +04:00
/**
2013-04-22 16:14:28 +04:00
* @ brief geo recursively through a dir and collect all files and sub files .
2013-04-22 13:58:39 +04:00
* @ param type $dir relative to the users files folder
* @ return array with list of files relative to the users files folder
*/
2013-05-09 16:43:06 +04:00
public function getAllFiles ( $dir ) {
2013-04-22 13:58:39 +04:00
$result = array ();
2013-05-01 13:04:40 +04:00
2013-05-09 16:43:06 +04:00
$content = $this -> view -> getDirectoryContent ( $this -> userFilesDir . $dir );
2013-05-01 13:04:40 +04:00
2013-05-09 16:43:06 +04:00
// handling for re shared folders
$path_split = explode ( '/' , $dir );
$shared = '' ;
if ( $path_split [ 1 ] === 'Shared' ) {
$shared = '/Shared' ;
}
2013-04-22 13:58:39 +04:00
2013-05-09 16:43:06 +04:00
foreach ( $content as $c ) {
$sharedPart = $path_split [ sizeof ( $path_split ) - 1 ];
$targetPathSplit = array_reverse ( explode ( '/' , $c [ 'path' ] ) );
2013-05-06 01:41:42 +04:00
2013-05-09 16:43:06 +04:00
$path = '' ;
2013-05-06 01:41:42 +04:00
2013-05-09 16:43:06 +04:00
// rebuild path
foreach ( $targetPathSplit as $pathPart ) {
if ( $pathPart !== $sharedPart ) {
$path = '/' . $pathPart . $path ;
} else {
break ;
}
}
2013-05-06 01:41:42 +04:00
2013-05-09 16:43:06 +04:00
$path = $dir . $path ;
2013-05-06 01:41:42 +04:00
2013-04-22 13:58:39 +04:00
if ( $c [ 'type' ] === " dir " ) {
2013-05-09 16:43:06 +04:00
$result = array_merge ( $result , $this -> getAllFiles ( $path ) );
2013-04-22 13:58:39 +04:00
} else {
2013-05-09 16:43:06 +04:00
$result [] = $path ;
2013-04-22 13:58:39 +04:00
}
}
2013-05-09 16:43:06 +04:00
2013-04-22 13:58:39 +04:00
return $result ;
2013-05-09 16:43:06 +04:00
2013-04-22 13:58:39 +04:00
}
2013-05-09 20:16:59 +04:00
/**
* @ brief get shares parent .
* @ param int $id of the current share
* @ return array of the parent
*/
public static function getShareParent ( $id ) {
2013-05-04 05:37:22 +04:00
2013-05-09 20:16:59 +04:00
$query = \OC_DB :: prepare ( 'SELECT `file_target`, `item_type`'
. ' FROM `*PREFIX*share`'
. ' WHERE `id` = ?' );
2013-05-04 05:37:22 +04:00
2013-05-09 20:16:59 +04:00
$result = $query -> execute ( array ( $id ) );
2013-05-04 05:37:22 +04:00
2013-05-09 20:16:59 +04:00
$row = $result -> fetchRow ();
2013-05-04 05:37:22 +04:00
2013-05-09 20:16:59 +04:00
return $row ;
2013-05-04 05:37:22 +04:00
2013-05-09 20:16:59 +04:00
}
2013-05-06 01:41:42 +04:00
2013-05-09 20:16:59 +04:00
/**
* @ brief get owner of the shared files .
* @ param int $Id of a share
* @ return owner
*/
public function getOwnerFromSharedFile ( $id ) {
2013-05-09 16:43:06 +04:00
2013-05-09 20:16:59 +04:00
$query = \OC_DB :: prepare ( 'SELECT `parent`, `uid_owner` FROM `*PREFIX*share` WHERE `id` = ?' , 1 );
$source = $query -> execute ( array ( $id ) ) -> fetchRow ();
if ( isset ( $source [ 'parent' ] ) ) {
2013-05-09 16:43:06 +04:00
2013-05-09 20:16:59 +04:00
$parent = $source [ 'parent' ];
2013-05-09 16:43:06 +04:00
2013-05-09 20:16:59 +04:00
while ( isset ( $parent ) ) {
2013-05-09 16:43:06 +04:00
2013-05-09 20:16:59 +04:00
$query = \OC_DB :: prepare ( 'SELECT `parent`, `uid_owner` FROM `*PREFIX*share` WHERE `id` = ?' , 1 );
$item = $query -> execute ( array ( $parent ) ) -> fetchRow ();
if ( isset ( $item [ 'parent' ] ) ) {
$parent = $item [ 'parent' ];
} else {
$fileOwner = $item [ 'uid_owner' ];
break ;
}
}
2013-05-09 16:43:06 +04:00
2013-05-09 20:16:59 +04:00
} else {
2013-05-09 16:43:06 +04:00
2013-05-09 20:16:59 +04:00
$fileOwner = $source [ 'uid_owner' ];
2013-05-09 16:43:06 +04:00
}
return $fileOwner ;
2013-05-09 20:16:59 +04:00
2013-05-09 16:43:06 +04:00
}
2013-05-06 01:41:42 +04:00
2012-07-11 20:51:27 +04:00
}