2012-07-11 20:51:27 +04:00
< ? php
/**
* ownCloud
*
* @ author Sam Tuke , Frank Karlitschek
2013-05-20 03:24:36 +04:00
* @ copyright 2012 Sam Tuke < samtuke @ owncloud . com > ,
2013-05-09 16:43:06 +04:00
* 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 />.
*
*/
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-05-27 14:41:55 +04:00
* @ param \OC_FilesystemView $view expected to have OC '/' as root path
2013-01-31 23:40:51 +04:00
* @ 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
*/
2013-05-27 14:41:55 +04:00
class Util {
2013-05-20 03:24:36 +04:00
2013-06-11 15:07:39 +04:00
const MIGRATION_COMPLETED = 1 ; // migration to new encryption completed
const MIGRATION_IN_PROGRESS = - 1 ; // migration is running
const MIGRATION_OPEN = 0 ; // user still needs to be migrated
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 $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
2013-05-13 17:15:35 +04:00
private $publicShareKeyId ;
2013-05-13 19:40:57 +04:00
private $recoveryKeyId ;
2013-05-20 03:24:36 +04:00
private $isPublic ;
2012-07-11 20:51:27 +04:00
2013-05-20 03:24:36 +04:00
/**
* @ param \OC_FilesystemView $view
* @ param $userId
* @ param bool $client
*/
2013-05-27 14:41:55 +04:00
public function __construct ( \OC_FilesystemView $view , $userId , $client = false ) {
2013-05-13 23:24:59 +04:00
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-05-20 03:24:36 +04:00
$this -> isPublic = false ;
2013-05-27 14:41:55 +04:00
$this -> publicShareKeyId = \OC_Appconfig :: getValue ( 'files_encryption' , 'publicShareKeyId' );
$this -> recoveryKeyId = \OC_Appconfig :: getValue ( 'files_encryption' , 'recoveryKeyId' );
2013-05-20 03:24:36 +04:00
// if we are anonymous/public
2013-05-28 11:27:04 +04:00
if ( \OCA\Encryption\Helper :: isPublicAccess ()) {
2013-05-20 03:24:36 +04:00
$this -> userId = $this -> publicShareKeyId ;
// only handle for files_sharing app
2013-05-28 11:36:14 +04:00
if ( isset ( $GLOBALS [ 'app' ]) && $GLOBALS [ 'app' ] === 'files_sharing' ) {
2013-05-20 03:24:36 +04:00
$this -> userDir = '/' . $GLOBALS [ 'fileOwner' ];
$this -> fileFolderName = 'files' ;
2013-05-27 14:41:55 +04:00
$this -> userFilesDir = '/' . $GLOBALS [ 'fileOwner' ] . '/'
. $this -> fileFolderName ; // TODO: Does this need to be user configurable?
2013-05-20 03:24:36 +04:00
$this -> publicKeyDir = '/' . 'public-keys' ;
$this -> encryptionDir = '/' . $GLOBALS [ 'fileOwner' ] . '/' . 'files_encryption' ;
$this -> keyfilesPath = $this -> encryptionDir . '/' . 'keyfiles' ;
$this -> shareKeysPath = $this -> encryptionDir . '/' . 'share-keys' ;
2013-05-27 14:41:55 +04:00
$this -> publicKeyPath =
$this -> publicKeyDir . '/' . $this -> userId . '.public.key' ; // e.g. data/public-keys/admin.public.key
$this -> privateKeyPath =
'/owncloud_private_key/' . $this -> userId . '.private.key' ; // e.g. data/admin/admin.private.key
2013-05-20 03:24:36 +04:00
$this -> isPublic = true ;
}
} else {
$this -> userDir = '/' . $this -> userId ;
$this -> fileFolderName = 'files' ;
2013-05-27 14:41:55 +04:00
$this -> userFilesDir =
'/' . $this -> userId . '/' . $this -> fileFolderName ; // TODO: Does this need to be user configurable?
2013-05-20 03:24:36 +04:00
$this -> publicKeyDir = '/' . 'public-keys' ;
$this -> encryptionDir = '/' . $this -> userId . '/' . 'files_encryption' ;
$this -> keyfilesPath = $this -> encryptionDir . '/' . 'keyfiles' ;
$this -> shareKeysPath = $this -> encryptionDir . '/' . 'share-keys' ;
2013-05-27 14:41:55 +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
2013-05-20 03:24:36 +04:00
}
2012-07-11 20:51:27 +04:00
}
2013-05-20 03:24:36 +04:00
/**
* @ return bool
*/
2013-05-24 01:56:31 +04:00
public function ready () {
2013-05-20 03:24:36 +04:00
if (
2013-05-27 14:41:55 +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
) {
2013-05-20 03:24:36 +04:00
2012-07-11 20:51:27 +04:00
return false ;
2013-05-20 03:24:36 +04:00
2012-07-11 20:51:27 +04:00
} else {
2013-05-20 03:24:36 +04:00
2012-07-11 20:51:27 +04:00
return true ;
2013-05-20 03:24:36 +04:00
2012-07-11 20:51:27 +04:00
}
2013-05-20 03:24:36 +04:00
2012-07-11 20:51:27 +04:00
}
2013-05-20 03:24:36 +04:00
/**
* @ brief Sets up user folders and keys for serverside encryption
2013-05-30 03:13:22 +04:00
*
* @ param string $passphrase to encrypt server - stored private key with
* @ return bool
2013-05-20 03:24:36 +04:00
*/
2013-05-27 14:41:55 +04:00
public function setupServerSide ( $passphrase = null ) {
2013-05-20 03:24:36 +04:00
2013-03-19 22:53:15 +04:00
// Set directories to check / create
2013-05-20 03:24:36 +04:00
$setUpDirs = array (
2013-05-27 14:41:55 +04:00
$this -> userDir ,
$this -> userFilesDir ,
$this -> publicKeyDir ,
$this -> encryptionDir ,
$this -> keyfilesPath ,
$this -> shareKeysPath
2013-03-19 22:53:15 +04:00
);
2013-05-20 03:24:36 +04:00
2013-03-19 22:53:15 +04:00
// Check / create all necessary dirs
2013-05-27 14:41:55 +04:00
foreach ( $setUpDirs as $dirPath ) {
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
if ( ! $this -> view -> file_exists ( $dirPath )) {
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
$this -> view -> mkdir ( $dirPath );
2013-05-20 03:24:36 +04:00
2013-03-19 22:53:15 +04:00
}
2013-05-20 03:24:36 +04:00
2013-01-14 19:39:04 +04:00
}
2013-05-20 03:24:36 +04:00
2012-07-11 20:51:27 +04:00
// Create user keypair
2013-05-20 23:22:03 +04:00
// we should never override a keyfile
2013-05-20 03:24:36 +04:00
if (
2013-05-27 14:41:55 +04:00
! $this -> view -> file_exists ( $this -> publicKeyPath )
&& ! $this -> view -> file_exists ( $this -> privateKeyPath )
2012-07-11 20:51:27 +04:00
) {
2013-05-20 03:24:36 +04:00
2012-07-11 20:51:27 +04:00
// Generate keypair
$keypair = Crypt :: createKeypair ();
2013-05-20 03:24:36 +04:00
2013-06-19 17:52:33 +04:00
if ( $keypair ) {
2013-05-20 03:24:36 +04:00
2013-06-19 17:52:33 +04:00
\OC_FileProxy :: $enabled = false ;
2013-05-20 03:24:36 +04:00
2013-06-19 17:52:33 +04:00
// Encrypt private key with user pwd as passphrase
$encryptedPrivateKey = Crypt :: symmetricEncryptFileContent ( $keypair [ 'privateKey' ], $passphrase );
2013-05-20 03:24:36 +04:00
2013-06-19 17:52:33 +04:00
// Save key-pair
if ( $encryptedPrivateKey ) {
$this -> view -> file_put_contents ( $this -> privateKeyPath , $encryptedPrivateKey );
$this -> view -> file_put_contents ( $this -> publicKeyPath , $keypair [ 'publicKey' ]);
}
2013-05-20 03:24:36 +04:00
2013-06-19 17:52:33 +04:00
\OC_FileProxy :: $enabled = true ;
}
2013-05-20 03:24:36 +04:00
2013-05-20 23:22:03 +04:00
} else {
// check if public-key exists but private-key is missing
2013-05-27 14:41:55 +04:00
if ( $this -> view -> file_exists ( $this -> publicKeyPath ) && ! $this -> view -> file_exists ( $this -> privateKeyPath )) {
\OCP\Util :: writeLog ( 'Encryption library' ,
'public key exists but private key is missing for "' . $this -> userId . '"' , \OCP\Util :: FATAL );
2013-05-20 23:22:03 +04:00
return false ;
2013-05-27 14:41:55 +04:00
} else {
if ( ! $this -> view -> file_exists ( $this -> publicKeyPath ) && $this -> view -> file_exists ( $this -> privateKeyPath )
) {
\OCP\Util :: writeLog ( 'Encryption library' ,
'private key exists but public key is missing for "' . $this -> userId . '"' , \OCP\Util :: FATAL );
return false ;
}
2013-05-20 23:22:03 +04:00
}
2013-05-01 21:18:31 +04:00
}
2013-05-20 03:24:36 +04:00
2013-05-01 21:18:31 +04:00
// If there's no record for this user's encryption preferences
2013-05-27 14:41:55 +04:00
if ( false === $this -> recoveryEnabledForUser ()) {
2013-05-20 03:24:36 +04:00
2013-05-01 21:18:31 +04:00
// create database configuration
2013-09-03 15:24:30 +04:00
$sql = 'INSERT INTO `*PREFIX*encryption` (`uid`,`mode`,`recovery_enabled`,`migration_status`) VALUES (?,?,?,?)' ;
2013-05-27 14:41:55 +04:00
$args = array (
$this -> userId ,
'server-side' ,
2013-09-03 15:24:30 +04:00
0 ,
2013-10-25 17:38:35 +04:00
self :: MIGRATION_OPEN
2013-05-27 14:41:55 +04:00
);
$query = \OCP\DB :: prepare ( $sql );
$query -> execute ( $args );
2013-05-20 03:24:36 +04:00
2012-07-11 20:51:27 +04:00
}
2013-05-20 03:24:36 +04:00
2012-11-16 22:31:37 +04:00
return true ;
2013-05-20 03:24:36 +04:00
2012-07-11 20:51:27 +04:00
}
2013-05-13 17:15:35 +04:00
2013-05-20 03:24:36 +04:00
/**
* @ return string
*/
2013-05-24 01:56:31 +04:00
public function getPublicShareKeyId () {
2013-05-13 17:15:35 +04:00
return $this -> publicShareKeyId ;
}
2013-05-20 03:24:36 +04:00
2013-03-20 22:26:59 +04:00
/**
* @ brief Check whether pwd recovery is enabled for a given user
2013-05-20 03:24:36 +04:00
* @ return bool 1 = yes , 0 = no , false = no record
*
* @ note If records are not being returned , check for a hidden space
2013-03-20 22:26:59 +04:00
* at the start of the uid in db
*/
2013-05-24 01:56:31 +04:00
public function recoveryEnabledForUser () {
2013-05-20 03:24:36 +04:00
2013-06-10 11:53:29 +04:00
$sql = 'SELECT `recovery_enabled` FROM `*PREFIX*encryption` WHERE `uid` = ?' ;
2013-03-19 22:53:15 +04:00
2013-05-27 14:41:55 +04:00
$args = array ( $this -> userId );
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
$query = \OCP\DB :: prepare ( $sql );
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
$result = $query -> execute ( $args );
2013-05-20 03:24:36 +04:00
2013-05-01 21:18:31 +04:00
$recoveryEnabled = array ();
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
if ( \OCP\DB :: isError ( $result )) {
\OCP\Util :: writeLog ( 'Encryption library' , \OC_DB :: getErrorMessage ( $result ), \OCP\Util :: ERROR );
2013-05-26 01:07:19 +04:00
} else {
2013-06-03 20:42:13 +04:00
if ( $result -> numRows () > 0 ) {
2013-05-27 14:41:55 +04:00
$row = $result -> fetchRow ();
if ( isset ( $row [ 'recovery_enabled' ])) {
$recoveryEnabled [] = $row [ 'recovery_enabled' ];
}
2013-05-26 01:07:19 +04:00
}
2013-03-20 22:26:59 +04:00
}
2013-05-20 03:24:36 +04:00
2013-05-01 21:18:31 +04:00
// If no record is found
2013-05-27 14:41:55 +04:00
if ( empty ( $recoveryEnabled )) {
2013-05-20 03:24:36 +04:00
2013-05-01 21:18:31 +04:00
return false ;
2013-05-20 03:24:36 +04:00
// If a record is found
2013-05-01 21:18:31 +04:00
} else {
2013-05-20 03:24:36 +04:00
2013-05-01 21:18:31 +04:00
return $recoveryEnabled [ 0 ];
2013-05-20 03:24:36 +04:00
2013-05-01 21:18:31 +04:00
}
2013-05-20 03:24:36 +04:00
2013-03-20 22:26:59 +04:00
}
2013-05-20 03:24:36 +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-27 14:41:55 +04:00
public function setRecoveryForUser ( $enabled ) {
2013-05-20 03:24:36 +04:00
2013-05-01 21:18:31 +04:00
$recoveryStatus = $this -> recoveryEnabledForUser ();
2013-05-20 03:24:36 +04:00
2013-05-01 21:18:31 +04:00
// If a record for this user already exists, update it
2013-05-27 14:41:55 +04:00
if ( false === $recoveryStatus ) {
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
$sql = 'INSERT INTO `*PREFIX*encryption` (`uid`,`mode`,`recovery_enabled`) VALUES (?,?,?)' ;
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
$args = array (
$this -> userId ,
'server-side' ,
$enabled
);
2013-05-20 03:24:36 +04:00
// Create a new record instead
2013-05-01 21:18:31 +04:00
} else {
2013-05-20 03:24:36 +04:00
2013-06-10 11:53:29 +04:00
$sql = 'UPDATE `*PREFIX*encryption` SET `recovery_enabled` = ? WHERE `uid` = ?' ;
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
$args = array (
$enabled ,
$this -> userId
);
2013-05-20 03:24:36 +04:00
2013-05-01 21:18:31 +04:00
}
2013-05-20 03:24:36 +04:00
2013-06-20 16:49:10 +04:00
return is_numeric ( \OC_DB :: executeAudited ( $sql , $args ));
2013-05-20 03:24:36 +04:00
2013-03-19 22:53:15 +04:00
}
2013-05-20 03:24:36 +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
2013-05-27 14:41:55 +04:00
* @ param bool $found the founded files if called again
2013-01-23 23:24:26 +04:00
* @ 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-05-27 14:41:55 +04:00
public function findEncFiles ( $directory , & $found = false ) {
2013-05-20 03:24:36 +04:00
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 ;
2013-05-20 03:24:36 +04:00
2013-05-27 22:44:38 +04:00
if ( $found === false ) {
2013-05-27 14:41:55 +04:00
$found = array (
'plain' => array (),
'encrypted' => array (),
'legacy' => array ()
);
2013-05-22 02:53:07 +04:00
}
2013-05-20 03:24:36 +04:00
if (
2013-05-27 14:41:55 +04:00
$this -> view -> is_dir ( $directory )
&& $handle = $this -> view -> opendir ( $directory )
2013-01-23 23:24:26 +04:00
) {
2013-09-05 13:58:57 +04:00
if ( is_resource ( $handle )) {
while ( false !== ( $file = readdir ( $handle ))) {
if (
$file !== " . "
&& $file !== " .. "
) {
$filePath = $directory . '/' . $this -> view -> getRelativePath ( '/' . $file );
$relPath = \OCA\Encryption\Helper :: stripUserFilesPath ( $filePath );
// If the path is a directory, search
// its contents
if ( $this -> view -> is_dir ( $filePath )) {
$this -> findEncFiles ( $filePath , $found );
// If the path is a file, determine
// its encryption status
} elseif ( $this -> view -> is_file ( $filePath )) {
// Disable proxies again, some-
// where they got re-enabled :/
\OC_FileProxy :: $enabled = false ;
$isEncryptedPath = $this -> isEncryptedPath ( $filePath );
// If the file is encrypted
// NOTE: If the userId is
// empty or not set, file will
// detected as plain
// NOTE: This is inefficient;
// scanning every file like this
// will eat server resources :(
if (
Keymanager :: getFileKey ( $this -> view , $this -> userId , $relPath )
&& $isEncryptedPath
) {
$found [ 'encrypted' ][] = array (
'name' => $file ,
'path' => $filePath
);
// If the file uses old
// encryption system
} elseif ( Crypt :: isLegacyEncryptedContent ( $isEncryptedPath , $relPath )) {
$found [ 'legacy' ][] = array (
'name' => $file ,
'path' => $filePath
);
// If the file is not encrypted
} else {
$found [ 'plain' ][] = array (
'name' => $file ,
'path' => $relPath
);
}
2013-05-20 03:24:36 +04:00
2012-07-31 22:28:11 +04:00
}
2013-05-20 03:24:36 +04:00
2012-07-31 22:28:11 +04:00
}
2013-05-20 03:24:36 +04:00
2012-07-31 22:28:11 +04:00
}
}
2013-05-20 03:24:36 +04:00
2013-01-23 23:24:26 +04:00
\OC_FileProxy :: $enabled = true ;
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
if ( empty ( $found )) {
2013-05-20 03:24:36 +04:00
2013-01-23 23:24:26 +04:00
return false ;
2013-05-20 03:24:36 +04:00
2012-07-31 22:28:11 +04:00
} else {
2013-05-20 03:24:36 +04:00
2013-01-23 23:24:26 +04:00
return $found ;
2013-05-20 03:24:36 +04:00
2012-07-31 22:28:11 +04:00
}
2013-05-20 03:24:36 +04:00
2012-07-31 22:28:11 +04:00
}
2013-05-20 03:24:36 +04:00
2013-01-23 23:24:26 +04:00
\OC_FileProxy :: $enabled = true ;
2013-05-20 03:24:36 +04:00
2012-07-31 22:28:11 +04:00
return false ;
}
2013-05-20 03:24:36 +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
*/
2013-05-27 14:41:55 +04:00
public function tail ( $filename , $numLines ) {
2013-05-20 03:24:36 +04:00
2013-03-09 22:18:34 +04:00
\OC_FileProxy :: $enabled = false ;
2013-05-20 03:24:36 +04:00
2013-03-09 22:18:34 +04:00
$text = '' ;
$pos = - 1 ;
2013-05-27 14:41:55 +04:00
$handle = $this -> view -> fopen ( $filename , 'r' );
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
while ( $numLines > 0 ) {
2013-03-09 22:18:34 +04:00
-- $pos ;
2013-05-27 14:41:55 +04:00
if ( fseek ( $handle , $pos , SEEK_END ) !== 0 ) {
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
rewind ( $handle );
2013-03-09 22:18:34 +04:00
$numLines = 0 ;
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
} elseif ( fgetc ( $handle ) === " \n " ) {
2013-05-20 03:24:36 +04:00
2013-03-09 22:18:34 +04:00
-- $numLines ;
2013-05-20 03:24:36 +04:00
2013-03-09 22:18:34 +04:00
}
2013-05-27 14:41:55 +04:00
$block_size = ( - $pos ) % 8192 ;
if ( $block_size === 0 || $numLines === 0 ) {
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
$text = fread ( $handle , ( $block_size === 0 ? 8192 : $block_size )) . $text ;
2013-05-20 03:24:36 +04:00
2013-03-09 22:18:34 +04:00
}
}
2013-05-27 14:41:55 +04:00
fclose ( $handle );
2013-05-20 03:24:36 +04:00
2013-03-09 22:18:34 +04:00
\OC_FileProxy :: $enabled = true ;
2013-05-20 03:24:36 +04:00
2013-03-09 22:18:34 +04:00
return $text ;
}
2013-05-20 03:24:36 +04:00
2013-05-09 20:16:59 +04:00
/**
2013-05-20 03:24:36 +04:00
* @ brief Check if a given path identifies an encrypted file
2013-05-30 03:13:22 +04:00
* @ param string $path
2013-05-20 03:24:36 +04:00
* @ return boolean
*/
2013-05-27 14:41:55 +04:00
public function isEncryptedPath ( $path ) {
2013-05-20 03:24:36 +04:00
2013-05-15 00:32:39 +04:00
// Disable encryption proxy so data retrieved is in its
2012-12-04 23:53:13 +04:00
// original form
2013-05-20 03:24:36 +04:00
$proxyStatus = \OC_FileProxy :: $enabled ;
2012-12-04 23:53:13 +04:00
\OC_FileProxy :: $enabled = false ;
2013-05-15 00:32:39 +04:00
2013-05-20 03:24:36 +04:00
// we only need 24 byte from the last chunk
$data = '' ;
2013-05-27 14:41:55 +04:00
$handle = $this -> view -> fopen ( $path , 'r' );
2013-05-28 22:50:14 +04:00
if ( is_resource ( $handle ) && ! fseek ( $handle , - 24 , SEEK_END )) {
2013-05-27 14:41:55 +04:00
$data = fgets ( $handle );
2013-05-20 03:24:36 +04:00
}
2013-05-15 00:32:39 +04:00
2013-05-20 03:24:36 +04:00
// re-enable proxy
2013-05-15 00:32:39 +04:00
\OC_FileProxy :: $enabled = $proxyStatus ;
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
return Crypt :: isCatfileContent ( $data );
2013-05-20 03:24:36 +04:00
2012-10-17 19:35:19 +04:00
}
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
/**
2013-05-20 03:24:36 +04:00
* @ brief get the file size of the unencrypted file
* @ param string $path absolute path
* @ return bool
*/
2013-05-27 14:41:55 +04:00
public function getFileSize ( $path ) {
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
$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-30 03:13:22 +04:00
// split the path parts
$pathParts = explode ( '/' , $path );
2013-06-03 20:42:13 +04:00
if ( isset ( $pathParts [ 2 ]) && $pathParts [ 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
2013-09-04 23:15:06 +04:00
$size = $this -> view -> filesize ( $path );
2013-04-27 02:05:20 +04:00
2013-10-05 20:00:46 +04:00
// fast path, else the calculation for $lastChunkNr is bogus
if ( $size === 0 ) {
\OC_FileProxy :: $enabled = $proxyStatus ;
return 0 ;
}
2013-05-09 20:16:59 +04:00
// calculate last chunk nr
2013-10-05 20:00:46 +04:00
// next highest is end of chunks, one subtracted is last one
// we have to read the last chunk, we can't just calculate it (because of padding etc)
$lastChunkNr = ceil ( $size / 8192 ) - 1 ;
2013-09-04 23:15:06 +04:00
$lastChunkSize = $size - ( $lastChunkNr * 8192 );
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
// open stream
2013-07-30 20:17:33 +04:00
$stream = fopen ( 'crypt://' . $path , " r " );
2013-04-27 02:05:20 +04:00
2013-05-27 14:41:55 +04:00
if ( is_resource ( $stream )) {
2013-05-09 20:16:59 +04:00
// calculate last chunk position
2013-05-27 14:41:55 +04:00
$lastChunckPos = ( $lastChunkNr * 8192 );
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
// seek to end
2013-05-27 14:41:55 +04:00
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
2013-09-04 23:15:06 +04:00
$lastChunkContent = fread ( $stream , $lastChunkSize );
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
2013-05-27 14:41:55 +04:00
$realSize = (( $lastChunkNr * 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 ;
}
2013-05-20 03:24:36 +04:00
2013-05-09 20:16:59 +04:00
/**
* @ brief fix the file size of the encrypted file
2013-05-27 14:41:55 +04:00
* @ param string $path absolute path
* @ return boolean true / false if file is encrypted
2013-05-09 20:16:59 +04:00
*/
2013-05-27 14:41:55 +04:00
public function fixFileSize ( $path ) {
2013-04-28 01:34:25 +04:00
2013-05-09 20:16:59 +04:00
$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-27 14:41:55 +04:00
$realSize = $this -> getFileSize ( $path );
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
if ( $realSize > 0 ) {
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
$cached = $this -> view -> getFileInfo ( $path );
2013-05-11 02:23:30 +04:00
$cached [ 'encrypted' ] = true ;
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
2013-05-27 14:41:55 +04:00
$this -> view -> putFileInfo ( $path , $cached );
2013-04-27 02:05:20 +04:00
2013-05-09 20:16:59 +04:00
$result = true ;
2013-05-20 03:24:36 +04:00
2013-05-09 20:16:59 +04:00
}
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-06-03 20:42:13 +04:00
2013-05-20 03:24:36 +04:00
/**
* @ param $path
* @ return bool
*/
2013-05-27 14:41:55 +04:00
public function isSharedPath ( $path ) {
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
$trimmed = ltrim ( $path , '/' );
$split = explode ( '/' , $trimmed );
2013-05-20 03:24:36 +04:00
2013-05-27 22:44:38 +04:00
if ( isset ( $split [ 2 ]) && $split [ 2 ] === 'Shared' ) {
2013-05-20 03:24:36 +04:00
2013-02-20 23:18:00 +04:00
return true ;
2013-05-20 03:24:36 +04:00
2013-02-20 23:18:00 +04:00
} else {
2013-05-20 03:24:36 +04:00
2013-02-20 23:18:00 +04:00
return false ;
2013-05-20 03:24:36 +04:00
2013-02-20 23:18:00 +04:00
}
2013-05-20 03:24:36 +04:00
2013-02-20 23:18:00 +04:00
}
2013-07-30 11:48:30 +04:00
2013-07-30 14:19:04 +04:00
/**
* @ brief encrypt versions from given file
* @ param array $filelist list of encrypted files , relative to data / user / files
* @ return boolean
*/
private function encryptVersions ( $filelist ) {
$successful = true ;
if ( \OCP\App :: isEnabled ( 'files_versions' )) {
foreach ( $filelist as $filename ) {
$versions = \OCA\Files_Versions\Storage :: getVersions ( $this -> userId , $filename );
foreach ( $versions as $version ) {
$path = '/' . $this -> userId . '/files_versions/' . $version [ 'path' ] . '.v' . $version [ 'version' ];
$encHandle = fopen ( 'crypt://' . $path . '.part' , 'wb' );
if ( $encHandle === false ) {
\OCP\Util :: writeLog ( 'Encryption library' , 'couldn\'t open "' . $path . '", decryption failed!' , \OCP\Util :: FATAL );
$successful = false ;
continue ;
}
$plainHandle = $this -> view -> fopen ( $path , 'rb' );
if ( $plainHandle === false ) {
\OCP\Util :: writeLog ( 'Encryption library' , 'couldn\'t open "' . $path . '.part", decryption failed!' , \OCP\Util :: FATAL );
$successful = false ;
continue ;
}
stream_copy_to_stream ( $plainHandle , $encHandle );
fclose ( $encHandle );
fclose ( $plainHandle );
$this -> view -> rename ( $path . '.part' , $path );
}
}
}
return $successful ;
}
/**
* @ brief decrypt versions from given file
* @ param string $filelist list of decrypted files , relative to data / user / files
* @ return boolean
*/
private function decryptVersions ( $filelist ) {
$successful = true ;
if ( \OCP\App :: isEnabled ( 'files_versions' )) {
foreach ( $filelist as $filename ) {
$versions = \OCA\Files_Versions\Storage :: getVersions ( $this -> userId , $filename );
foreach ( $versions as $version ) {
$path = '/' . $this -> userId . '/files_versions/' . $version [ 'path' ] . '.v' . $version [ 'version' ];
$encHandle = fopen ( 'crypt://' . $path , 'rb' );
if ( $encHandle === false ) {
\OCP\Util :: writeLog ( 'Encryption library' , 'couldn\'t open "' . $path . '", decryption failed!' , \OCP\Util :: FATAL );
$successful = false ;
continue ;
}
$plainHandle = $this -> view -> fopen ( $path . '.part' , 'wb' );
if ( $plainHandle === false ) {
\OCP\Util :: writeLog ( 'Encryption library' , 'couldn\'t open "' . $path . '.part", decryption failed!' , \OCP\Util :: FATAL );
$successful = false ;
continue ;
}
stream_copy_to_stream ( $encHandle , $plainHandle );
fclose ( $encHandle );
fclose ( $plainHandle );
$this -> view -> rename ( $path . '.part' , $path );
}
}
}
return $successful ;
}
2013-08-18 13:21:01 +04:00
2013-07-29 19:06:05 +04:00
/**
* @ brief Decrypt all files
* @ return bool
*/
public function decryptAll () {
$found = $this -> findEncFiles ( $this -> userId . '/files' );
2013-07-30 11:48:30 +04:00
$successful = true ;
2013-07-29 19:06:05 +04:00
if ( $found ) {
2013-07-30 14:19:04 +04:00
$versionStatus = \OCP\App :: isEnabled ( 'files_versions' );
\OC_App :: disable ( 'files_versions' );
2013-08-18 13:21:01 +04:00
2013-08-12 15:59:49 +04:00
$decryptedFiles = array ();
2013-07-30 14:19:04 +04:00
2013-07-29 19:06:05 +04:00
// Encrypt unencrypted files
foreach ( $found [ 'encrypted' ] as $encryptedFile ) {
2013-08-18 13:21:01 +04:00
//get file info
$fileInfo = \OC\Files\Filesystem :: getFileInfo ( $encryptedFile [ 'path' ]);
2013-07-29 19:06:05 +04:00
//relative to data/<user>/file
$relPath = Helper :: stripUserFilesPath ( $encryptedFile [ 'path' ]);
//relative to /data
$rawPath = $encryptedFile [ 'path' ];
2013-08-18 13:21:01 +04:00
2013-08-12 16:30:43 +04:00
//get timestamp
$timestamp = $this -> view -> filemtime ( $rawPath );
2013-08-18 13:21:01 +04:00
2013-07-31 18:35:14 +04:00
//enable proxy to use OC\Files\View to access the original file
\OC_FileProxy :: $enabled = true ;
2013-07-29 19:06:05 +04:00
// Open enc file handle for binary reading
2013-07-31 18:35:14 +04:00
$encHandle = $this -> view -> fopen ( $rawPath , 'rb' );
// Disable proxy to prevent file being encrypted again
\OC_FileProxy :: $enabled = false ;
2013-07-29 19:06:05 +04:00
2013-07-30 11:48:30 +04:00
if ( $encHandle === false ) {
\OCP\Util :: writeLog ( 'Encryption library' , 'couldn\'t open "' . $rawPath . '", decryption failed!' , \OCP\Util :: FATAL );
$successful = false ;
continue ;
}
2013-07-29 19:06:05 +04:00
// Open plain file handle for binary writing, with same filename as original plain file
$plainHandle = $this -> view -> fopen ( $rawPath . '.part' , 'wb' );
2013-07-30 11:48:30 +04:00
if ( $plainHandle === false ) {
\OCP\Util :: writeLog ( 'Encryption library' , 'couldn\'t open "' . $rawPath . '.part", decryption failed!' , \OCP\Util :: FATAL );
$successful = false ;
continue ;
}
2013-07-29 19:06:05 +04:00
// Move plain file to a temporary location
$size = stream_copy_to_stream ( $encHandle , $plainHandle );
2013-07-30 11:48:30 +04:00
if ( $size === 0 ) {
\OCP\Util :: writeLog ( 'Encryption library' , 'Zero bytes copied of "' . $rawPath . '", decryption failed!' , \OCP\Util :: FATAL );
$successful = false ;
continue ;
}
2013-07-29 19:06:05 +04:00
fclose ( $encHandle );
fclose ( $plainHandle );
$fakeRoot = $this -> view -> getRoot ();
$this -> view -> chroot ( '/' . $this -> userId . '/files' );
$this -> view -> rename ( $relPath . '.part' , $relPath );
$this -> view -> chroot ( $fakeRoot );
2013-08-12 16:30:43 +04:00
//set timestamp
$this -> view -> touch ( $rawPath , $timestamp );
2013-08-18 13:21:01 +04:00
2013-07-29 19:06:05 +04:00
// Add the file to the cache
\OC\Files\Filesystem :: putFileInfo ( $relPath , array (
'encrypted' => false ,
'size' => $size ,
2013-08-18 13:21:01 +04:00
'unencrypted_size' => $size ,
'etag' => $fileInfo [ 'etag' ]
2013-07-29 19:06:05 +04:00
));
2013-08-18 13:21:01 +04:00
2013-07-30 14:19:04 +04:00
$decryptedFiles [] = $relPath ;
2013-07-29 19:06:05 +04:00
}
2013-07-30 14:19:04 +04:00
if ( $versionStatus ) {
\OC_App :: enable ( 'files_versions' );
}
2013-08-18 13:21:01 +04:00
2013-07-30 14:19:04 +04:00
if ( ! $this -> decryptVersions ( $decryptedFiles )) {
$successful = false ;
}
2013-08-18 13:21:01 +04:00
2013-07-30 11:48:30 +04:00
if ( $successful ) {
$this -> view -> deleteAll ( $this -> keyfilesPath );
$this -> view -> deleteAll ( $this -> shareKeysPath );
}
2013-07-29 19:06:05 +04:00
\OC_FileProxy :: $enabled = true ;
}
2013-07-30 11:48:30 +04:00
return $successful ;
2013-07-29 19:06:05 +04:00
}
2013-05-20 03:24:36 +04:00
2013-01-23 23:24:26 +04:00
/**
* @ brief Encrypt all files in a directory
* @ param string $dirPath the directory whose files will be encrypted
2013-05-20 03:24:36 +04:00
* @ param null $legacyPassphrase
* @ param null $newPassphrase
* @ return bool
2013-01-23 23:24:26 +04:00
* @ note Encryption is recursive
*/
2013-05-27 14:41:55 +04:00
public function encryptAll ( $dirPath , $legacyPassphrase = null , $newPassphrase = null ) {
2013-05-17 16:13:05 +04:00
2013-07-29 19:06:05 +04:00
$found = $this -> findEncFiles ( $dirPath );
2013-07-30 14:19:04 +04:00
2013-07-29 19:06:05 +04:00
if ( $found ) {
2013-05-17 16:13:05 +04:00
2013-01-29 23:54:40 +04:00
// Disable proxy to prevent file being encrypted twice
\OC_FileProxy :: $enabled = false ;
2013-08-18 13:21:01 +04:00
2013-07-30 14:19:04 +04:00
$versionStatus = \OCP\App :: isEnabled ( 'files_versions' );
\OC_App :: disable ( 'files_versions' );
2013-08-18 13:21:01 +04:00
2013-07-30 14:19:04 +04:00
$encryptedFiles = array ();
2013-05-17 16:13:05 +04:00
2013-01-23 23:24:26 +04:00
// Encrypt unencrypted files
2013-05-27 14:41:55 +04:00
foreach ( $found [ 'plain' ] as $plainFile ) {
2013-05-17 16:13:05 +04:00
2013-08-18 13:21:01 +04:00
//get file info
$fileInfo = \OC\Files\Filesystem :: getFileInfo ( $plainFile [ 'path' ]);
2013-04-12 17:18:19 +04:00
//relative to data/<user>/file
$relPath = $plainFile [ 'path' ];
2013-08-18 13:21:01 +04:00
2013-04-12 17:18:19 +04:00
//relative to /data
2013-06-03 20:42:13 +04:00
$rawPath = '/' . $this -> userId . '/files/' . $plainFile [ 'path' ];
2013-05-17 16:13:05 +04:00
2013-08-12 16:30:43 +04:00
// keep timestamp
$timestamp = $this -> view -> filemtime ( $rawPath );
2013-08-18 13:21:01 +04:00
2013-04-23 20:41:01 +04:00
// Open plain file handle for binary reading
2013-06-03 20:42:13 +04:00
$plainHandle = $this -> view -> fopen ( $rawPath , 'rb' );
2013-05-17 16:13:05 +04:00
2013-05-24 19:35:00 +04:00
// Open enc file handle for binary writing, with same filename as original plain file
2013-07-29 19:06:05 +04:00
$encHandle = fopen ( 'crypt://' . $rawPath . '.part' , 'wb' );
2013-05-17 16:13:05 +04:00
2013-04-23 20:41:01 +04:00
// Move plain file to a temporary location
2013-06-03 20:42:13 +04:00
$size = stream_copy_to_stream ( $plainHandle , $encHandle );
2013-05-17 16:13:05 +04:00
2013-05-24 19:35:00 +04:00
fclose ( $encHandle );
2013-07-29 19:06:05 +04:00
fclose ( $plainHandle );
2013-05-17 16:13:05 +04:00
2013-05-24 19:35:00 +04:00
$fakeRoot = $this -> view -> getRoot ();
2013-06-03 20:42:13 +04:00
$this -> view -> chroot ( '/' . $this -> userId . '/files' );
2013-05-22 04:02:42 +04:00
2013-06-13 22:52:34 +04:00
$this -> view -> rename ( $relPath . '.part' , $relPath );
2013-05-17 16:13:05 +04:00
2013-05-24 19:35:00 +04:00
$this -> view -> chroot ( $fakeRoot );
2013-08-18 13:21:01 +04:00
2013-08-12 16:30:43 +04:00
// set timestamp
$this -> view -> touch ( $rawPath , $timestamp );
2013-05-17 16:13:05 +04:00
2013-01-31 23:40:51 +04:00
// Add the file to the cache
2013-06-03 20:42:13 +04:00
\OC\Files\Filesystem :: putFileInfo ( $relPath , array (
2013-07-29 19:06:05 +04:00
'encrypted' => true ,
'size' => $size ,
2013-08-18 13:21:01 +04:00
'unencrypted_size' => $size ,
'etag' => $fileInfo [ 'etag' ]
2013-07-29 19:06:05 +04:00
));
2013-08-18 13:21:01 +04:00
2013-07-30 14:19:04 +04:00
$encryptedFiles [] = $relPath ;
2013-01-23 23:24:26 +04:00
}
2013-05-17 16:13:05 +04:00
2013-01-23 23:24:26 +04:00
// Encrypt legacy encrypted files
2013-05-17 16:13:05 +04:00
if (
2013-05-27 14:41:55 +04:00
! empty ( $legacyPassphrase )
&& ! empty ( $newPassphrase )
2013-01-24 22:37:34 +04:00
) {
2013-05-17 16:13:05 +04:00
2013-05-27 14:41:55 +04:00
foreach ( $found [ 'legacy' ] as $legacyFile ) {
2013-05-17 16:13:05 +04:00
2013-01-24 22:37:34 +04:00
// Fetch data from file
2013-05-27 14:41:55 +04:00
$legacyData = $this -> view -> file_get_contents ( $legacyFile [ 'path' ]);
2013-05-17 16:13:05 +04:00
2013-05-31 03:36:49 +04:00
// decrypt data, generate catfile
$decrypted = Crypt :: legacyBlockDecrypt ( $legacyData , $legacyPassphrase );
2013-05-17 16:13:05 +04:00
2013-05-17 13:15:36 +04:00
$rawPath = $legacyFile [ 'path' ];
2013-05-17 16:13:05 +04:00
2013-05-31 03:36:49 +04:00
// enable proxy the ensure encryption is handled
\OC_FileProxy :: $enabled = true ;
// Open enc file handle for binary writing, with same filename as original plain file
$encHandle = $this -> view -> fopen ( $rawPath , 'wb' );
2013-05-17 13:15:36 +04:00
2013-05-31 03:36:49 +04:00
if ( is_resource ( $encHandle )) {
2013-05-17 13:15:36 +04:00
2013-05-31 03:36:49 +04:00
// write data to stream
fwrite ( $encHandle , $decrypted );
2013-05-17 16:13:05 +04:00
2013-05-31 03:36:49 +04:00
// close stream
fclose ( $encHandle );
}
2013-05-17 16:13:05 +04:00
2013-05-31 03:36:49 +04:00
// disable proxy to prevent file being encrypted twice
\OC_FileProxy :: $enabled = false ;
2013-01-24 22:37:34 +04:00
}
2013-01-23 23:24:26 +04:00
}
2013-05-17 16:13:05 +04:00
2013-01-29 23:54:40 +04:00
\OC_FileProxy :: $enabled = true ;
2013-05-17 16:13:05 +04:00
2013-07-30 14:19:04 +04:00
if ( $versionStatus ) {
\OC_App :: enable ( 'files_versions' );
}
2013-08-18 13:21:01 +04:00
2013-07-30 14:19:04 +04:00
$this -> encryptVersions ( $encryptedFiles );
2013-08-18 13:21:01 +04:00
2013-01-29 23:54:40 +04:00
// If files were found, return true
return true ;
} else {
2013-05-17 16:13:05 +04:00
2013-01-29 23:54:40 +04:00
// If no files were found, return false
return false ;
2012-07-31 22:28:11 +04:00
}
}
2013-05-17 16:13:05 +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
*/
2013-05-27 14:41:55 +04:00
public function getPath ( $pathName ) {
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
switch ( $pathName ) {
2013-05-20 03:24:36 +04:00
2012-11-16 22:31:37 +04:00
case 'publicKeyDir' :
2013-05-20 03:24:36 +04:00
2012-11-16 22:31:37 +04:00
return $this -> publicKeyDir ;
2013-05-20 03:24:36 +04:00
2012-11-16 22:31:37 +04:00
break ;
2013-05-20 03:24:36 +04:00
2012-11-16 22:31:37 +04:00
case 'encryptionDir' :
2013-05-20 03:24:36 +04:00
2012-11-16 22:31:37 +04:00
return $this -> encryptionDir ;
2013-05-20 03:24:36 +04:00
2012-11-16 22:31:37 +04:00
break ;
2013-05-20 03:24:36 +04:00
2012-11-16 22:31:37 +04:00
case 'keyfilesPath' :
2013-05-20 03:24:36 +04:00
2012-11-16 22:31:37 +04:00
return $this -> keyfilesPath ;
2013-05-20 03:24:36 +04:00
2012-11-16 22:31:37 +04:00
break ;
2013-05-20 03:24:36 +04:00
2012-11-16 22:31:37 +04:00
case 'publicKeyPath' :
2013-05-20 03:24:36 +04:00
2012-11-16 22:31:37 +04:00
return $this -> publicKeyPath ;
2013-05-20 03:24:36 +04:00
2012-11-16 22:31:37 +04:00
break ;
2013-05-20 03:24:36 +04:00
2012-11-16 22:31:37 +04:00
case 'privateKeyPath' :
2013-05-20 03:24:36 +04:00
2012-11-16 22:31:37 +04:00
return $this -> privateKeyPath ;
2013-05-20 03:24:36 +04:00
2012-11-16 22:31:37 +04:00
break ;
}
2013-05-20 03:24:36 +04:00
2013-05-24 01:56:31 +04:00
return false ;
2012-11-16 22:31:37 +04:00
}
2013-05-20 03:24:36 +04:00
2013-02-12 19:48:04 +04:00
/**
* @ brief get path of a file .
2013-05-20 03:24:36 +04:00
* @ param int $fileId id of the file
* @ return string path of the file
2013-02-12 19:48:04 +04:00
*/
2013-05-27 14:41:55 +04:00
public static function fileIdToPath ( $fileId ) {
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
$sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?' ;
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
$query = \OCP\DB :: prepare ( $sql );
$result = $query -> execute ( array ( $fileId ));
2013-05-20 03:24:36 +04:00
2013-05-26 01:07:19 +04:00
$path = false ;
2013-05-27 14:41:55 +04:00
if ( \OCP\DB :: isError ( $result )) {
\OCP\Util :: writeLog ( 'Encryption library' , \OC_DB :: getErrorMessage ( $result ), \OCP\Util :: ERROR );
2013-05-26 01:07:19 +04:00
} else {
2013-06-03 20:42:13 +04:00
if ( $result -> numRows () > 0 ) {
2013-05-27 14:41:55 +04:00
$row = $result -> fetchRow ();
$path = substr ( $row [ 'path' ], strlen ( 'files' ));
}
2013-05-25 23:33:05 +04:00
}
2013-05-26 01:07:19 +04:00
return $path ;
2013-05-20 03:24:36 +04:00
2013-02-12 19:48:04 +04:00
}
2013-05-20 03:24:36 +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-05-27 14:41:55 +04:00
* @ return array as multi - dimensional array . keys : ready , unready
2013-02-19 23:16:50 +04:00
*/
2013-05-27 14:41:55 +04:00
public function filterShareReadyUsers ( $unfilteredUsers ) {
2013-05-20 03:24:36 +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-05-20 03:24:36 +04:00
2013-02-19 23:16:50 +04:00
// Loop through users and create array of UIDs that need new keyfiles
2013-05-27 14:41:55 +04:00
foreach ( $unfilteredUsers as $user ) {
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
$util = new Util ( $this -> view , $user );
2013-05-20 03:24:36 +04:00
2013-02-19 23:16:50 +04:00
// Check that the user is encryption capable, or is the
// public system user 'ownCloud' (for public shares)
2013-05-20 03:24:36 +04:00
if (
2013-05-27 22:44:38 +04:00
$user === $this -> publicShareKeyId
or $user === $this -> recoveryKeyId
2013-05-20 03:24:36 +04:00
or $util -> ready ()
2013-02-19 23:16:50 +04:00
) {
2013-05-20 03:24:36 +04:00
2013-03-30 00:11:29 +04:00
// Construct array of ready UIDs for Keymanager{}
$readyIds [] = $user ;
2013-05-20 03:24:36 +04:00
2013-02-19 23:16:50 +04:00
} else {
2013-05-20 03:24:36 +04:00
2013-03-30 00:11:29 +04:00
// Construct array of unready UIDs for Keymanager{}
$unreadyIds [] = $user ;
2013-05-20 03:24:36 +04:00
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
2013-05-27 14:41:55 +04:00
\OCP\Util :: writeLog ( 'Encryption library' ,
'"' . $user . '" is not setup for encryption' , \OCP\Util :: WARN );
2013-05-20 03:24:36 +04:00
2013-02-19 23:16:50 +04:00
}
2013-05-20 03:24:36 +04:00
2013-02-19 23:16:50 +04:00
}
2013-05-20 03:24:36 +04:00
return array (
2013-05-24 01:56:31 +04:00
'ready' => $readyIds ,
'unready' => $unreadyIds
2013-03-30 00:11:29 +04:00
);
2013-05-20 03:24:36 +04:00
2013-02-19 23:16:50 +04:00
}
2013-05-20 03:24:36 +04:00
2013-02-20 23:18:00 +04:00
/**
2013-08-12 18:19:08 +04:00
* @ brief Decrypt a keyfile
2013-02-27 22:46:44 +04:00
* @ param string $filePath
* @ param string $privateKey
2013-05-30 03:13:22 +04:00
* @ return bool | string
2013-02-20 23:18:00 +04:00
*/
2013-08-12 18:19:08 +04:00
private function decryptKeyfile ( $filePath , $privateKey ) {
2013-02-20 23:18:00 +04:00
// Get the encrypted keyfile
2013-05-27 14:41:55 +04:00
$encKeyfile = Keymanager :: getFileKey ( $this -> view , $this -> userId , $filePath );
2013-05-20 03:24:36 +04:00
2013-08-12 18:19:08 +04:00
// The file has a shareKey and must use it for decryption
$shareKey = Keymanager :: getShareKey ( $this -> view , $this -> userId , $filePath );
2013-05-20 03:24:36 +04:00
2013-08-12 18:19:08 +04:00
$plainKeyfile = Crypt :: multiKeyDecrypt ( $encKeyfile , $shareKey , $privateKey );
2013-05-20 03:24:36 +04:00
2013-02-27 22:46:44 +04:00
return $plainKeyfile ;
}
2013-05-20 03:24:36 +04:00
2013-02-27 22:46:44 +04:00
/**
* @ brief Encrypt keyfile to multiple users
2013-05-20 03:24:36 +04:00
* @ param Session $session
2013-02-27 22:46:44 +04:00
* @ 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-20 03:24:36 +04:00
* @ return bool
2013-02-27 22:46:44 +04:00
*/
2013-05-27 14:41:55 +04:00
public function setSharedFileKeyfiles ( Session $session , array $users , $filePath ) {
2013-05-20 03:24:36 +04:00
2013-02-27 22:46:44 +04:00
// Make sure users are capable of sharing
2013-05-27 14:41:55 +04:00
$filteredUids = $this -> filterShareReadyUsers ( $users );
2013-05-20 03:24:36 +04:00
2013-05-09 16:43:06 +04:00
// If we're attempting to share to unready users
2013-05-27 14:41:55 +04:00
if ( ! empty ( $filteredUids [ 'unready' ])) {
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
\OCP\Util :: writeLog ( 'Encryption library' ,
'Sharing to these user(s) failed as they are unready for encryption:"'
. print_r ( $filteredUids [ 'unready' ], 1 ), \OCP\Util :: WARN );
2013-05-20 03:24:36 +04:00
2013-05-09 16:43:06 +04:00
return false ;
2013-05-20 03:24:36 +04:00
2013-03-30 00:11:29 +04:00
}
2013-05-20 03:24:36 +04:00
2013-02-27 22:46:44 +04:00
// Get public keys for each user, ready for generating sharekeys
2013-05-27 14:41:55 +04:00
$userPubKeys = Keymanager :: getPublicKeys ( $this -> view , $filteredUids [ 'ready' ]);
2013-05-20 03:24:36 +04:00
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 ();
2013-05-16 16:53:04 +04:00
2013-05-27 14:41:55 +04:00
$fileOwner = \OC\Files\Filesystem :: getOwner ( $filePath );
2013-05-20 03:24:36 +04:00
2013-03-19 22:53:15 +04:00
// Decrypt keyfile
2013-08-12 18:19:08 +04:00
$plainKeyfile = $this -> decryptKeyfile ( $filePath , $privateKey );
2013-05-20 03:24:36 +04:00
2013-02-20 23:18:00 +04:00
// Re-enc keyfile to (additional) sharekeys
2013-05-27 14:41:55 +04:00
$multiEncKey = Crypt :: multiKeyEncrypt ( $plainKeyfile , $userPubKeys );
2013-05-20 03:24:36 +04:00
2013-02-26 22:11:29 +04:00
// Save the recrypted key to it's owner's keyfiles directory
// Save new sharekeys to all necessary user directory
2013-05-20 03:24:36 +04:00
if (
2013-05-27 14:41:55 +04:00
! Keymanager :: setFileKey ( $this -> view , $filePath , $fileOwner , $multiEncKey [ 'data' ])
|| ! Keymanager :: setShareKeys ( $this -> view , $filePath , $multiEncKey [ 'keys' ])
2013-02-26 22:11:29 +04:00
) {
2013-02-20 23:18:00 +04:00
2013-05-27 14:41:55 +04:00
\OCP\Util :: writeLog ( 'Encryption library' ,
'Keyfiles could not be saved for users sharing ' . $filePath , \OCP\Util :: ERROR );
2013-05-20 03:24:36 +04:00
2013-05-09 16:43:06 +04:00
return false ;
2013-02-20 23:18:00 +04:00
}
2013-05-20 03:24:36 +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-05-20 03:24:36 +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-05-27 14:41: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 (
2013-05-27 14:41:55 +04:00
\OC_Appconfig :: getValue ( 'files_encryption' , 'recoveryAdminEnabled' )
2013-05-04 18:14:38 +04:00
&& $this -> recoveryEnabledForUser ()
) {
$recoveryEnabled = true ;
} else {
$recoveryEnabled = false ;
}
2013-05-20 03:24:36 +04:00
2013-04-16 20:29:22 +04:00
// Make sure that a share key is generated for the owner too
2013-05-27 14:41:55 +04:00
list ( $owner , $ownerPath ) = $this -> getUidAndFilename ( $filePath );
2013-05-20 03:24:36 +04:00
2013-08-27 18:29:54 +04:00
$pathinfo = pathinfo ( $ownerPath );
if ( array_key_exists ( 'extension' , $pathinfo ) && $pathinfo [ 'extension' ] === 'part' ) {
$ownerPath = $pathinfo [ 'dirname' ] . '/' . $pathinfo [ 'filename' ];
}
2013-05-24 01:56:31 +04:00
$userIds = array ();
2013-05-27 14:41:55 +04:00
if ( $sharingEnabled ) {
2013-04-18 19:53:59 +04:00
2013-04-16 20:29:22 +04:00
// Find out who, if anyone, is sharing the file
2013-05-29 15:10:26 +04:00
$result = \OCP\Share :: getUsersSharingFile ( $ownerPath , $owner , true );
2013-05-13 19:40:57 +04:00
$userIds = $result [ 'users' ];
2013-05-27 14:41:55 +04:00
if ( $result [ 'public' ]) {
2013-05-13 19:40:57 +04:00
$userIds [] = $this -> publicShareKeyId ;
}
2013-05-20 03:24:36 +04:00
2013-04-16 20:29:22 +04:00
}
2013-05-20 03:24:36 +04:00
2013-08-18 13:21:01 +04:00
// If recovery is enabled, add the
2013-04-16 20:29:22 +04:00
// Admin UID to list of users to share to
2013-05-27 14:41:55 +04:00
if ( $recoveryEnabled ) {
2013-05-04 18:14:38 +04:00
// Find recoveryAdmin user ID
2013-05-27 14:41:55 +04:00
$recoveryKeyId = \OC_Appconfig :: getValue ( 'files_encryption' , 'recoveryKeyId' );
2013-05-04 18:14:38 +04:00
// Add recoveryAdmin to list of users sharing
2013-05-13 19:26:21 +04:00
$userIds [] = $recoveryKeyId ;
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
2013-05-27 22:44:38 +04:00
if ( $currentUserId !== false ) {
2013-05-09 16:43:06 +04:00
$userIds [] = $currentUserId ;
2013-06-25 14:21:54 +04:00
}
2013-05-20 03:24:36 +04:00
2013-06-25 14:21:54 +04:00
// check if it is a group mount
2013-06-25 19:46:04 +04:00
if ( \OCP\App :: isEnabled ( " files_external " )) {
$mount = \OC_Mount_Config :: getSystemMountPoints ();
foreach ( $mount as $mountPoint => $data ) {
if ( $mountPoint == substr ( $ownerPath , 1 , strlen ( $mountPoint ))) {
$userIds = array_merge ( $userIds , $this -> getUserWithAccessToMountPoint ( $data [ 'applicable' ][ 'users' ], $data [ 'applicable' ][ 'groups' ]));
}
2013-06-25 14:21:54 +04:00
}
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
2013-05-27 14:41:55 +04:00
$uniqueUserIds = array_unique ( $userIds );
2013-05-20 03:24:36 +04:00
2013-04-16 20:29:22 +04:00
return $uniqueUserIds ;
}
2013-05-20 03:24:36 +04:00
2013-06-25 14:21:54 +04:00
private function getUserWithAccessToMountPoint ( $users , $groups ) {
$result = array ();
if ( in_array ( 'all' , $users )) {
$result = \OCP\User :: getUsers ();
} else {
$result = array_merge ( $result , $users );
foreach ( $groups as $group ) {
$result = array_merge ( $result , \OC_Group :: usersInGroup ( $group ));
}
}
return $result ;
}
2013-05-04 18:14:38 +04:00
/**
2013-06-11 14:03:50 +04:00
* @ brief start migration mode to initially encrypt users data
* @ return boolean
2013-05-04 18:14:38 +04:00
*/
2013-06-11 14:03:50 +04:00
public function beginMigration () {
2013-05-20 03:24:36 +04:00
2013-06-12 14:21:11 +04:00
$return = false ;
2013-05-20 03:24:36 +04:00
2013-06-12 14:21:11 +04:00
$sql = 'UPDATE `*PREFIX*encryption` SET `migration_status` = ? WHERE `uid` = ? and `migration_status` = ?' ;
$args = array ( self :: MIGRATION_IN_PROGRESS , $this -> userId , self :: MIGRATION_OPEN );
2013-05-27 14:41:55 +04:00
$query = \OCP\DB :: prepare ( $sql );
2013-06-20 16:49:10 +04:00
$manipulatedRows = $query -> execute ( $args );
2013-05-20 03:24:36 +04:00
2013-06-12 14:21:11 +04:00
if ( $manipulatedRows === 1 ) {
$return = true ;
\OCP\Util :: writeLog ( 'Encryption library' , " Start migration to encryption mode for " . $this -> userId , \OCP\Util :: INFO );
2013-06-11 14:03:50 +04:00
} else {
2013-06-12 14:21:11 +04:00
\OCP\Util :: writeLog ( 'Encryption library' , " Could not activate migration mode for " . $this -> userId . " . Probably another process already started the initial encryption " , \OCP\Util :: WARN );
2013-06-11 14:03:50 +04:00
}
2013-05-20 03:24:36 +04:00
2013-06-11 14:03:50 +04:00
return $return ;
}
2013-05-20 03:24:36 +04:00
2013-06-11 14:03:50 +04:00
/**
* @ brief close migration mode after users data has been encrypted successfully
* @ return boolean
*/
public function finishMigration () {
2013-05-20 03:24:36 +04:00
2013-06-11 14:03:50 +04:00
$return = false ;
2013-05-20 03:24:36 +04:00
2013-06-12 14:21:11 +04:00
$sql = 'UPDATE `*PREFIX*encryption` SET `migration_status` = ? WHERE `uid` = ? and `migration_status` = ?' ;
$args = array ( self :: MIGRATION_COMPLETED , $this -> userId , self :: MIGRATION_IN_PROGRESS );
$query = \OCP\DB :: prepare ( $sql );
2013-06-20 16:49:10 +04:00
$manipulatedRows = $query -> execute ( $args );
2013-05-20 03:24:36 +04:00
2013-06-12 14:21:11 +04:00
if ( $manipulatedRows === 1 ) {
2013-06-13 12:11:23 +04:00
$return = true ;
2013-06-12 14:21:11 +04:00
\OCP\Util :: writeLog ( 'Encryption library' , " Finish migration successfully for " . $this -> userId , \OCP\Util :: INFO );
2013-06-11 14:03:50 +04:00
} else {
2013-06-12 14:21:11 +04:00
\OCP\Util :: writeLog ( 'Encryption library' , " Could not deactivate migration mode for " . $this -> userId , \OCP\Util :: WARN );
2013-05-04 18:14:38 +04:00
}
2013-05-20 03:24:36 +04:00
2013-06-11 14:03:50 +04:00
return $return ;
2013-05-04 18:14:38 +04:00
}
2013-05-20 03:24:36 +04:00
2013-05-04 18:14:38 +04:00
/**
2013-06-11 14:03:50 +04:00
* @ brief check if files are already migrated to the encryption system
2013-06-11 15:07:39 +04:00
* @ return migration status , false = in case of no record
2013-05-20 03:24:36 +04:00
* @ note If records are not being returned , check for a hidden space
2013-05-04 18:14:38 +04:00
* at the start of the uid in db
*/
2013-05-24 01:56:31 +04:00
public function getMigrationStatus () {
2013-05-20 03:24:36 +04:00
2013-06-10 11:53:29 +04:00
$sql = 'SELECT `migration_status` FROM `*PREFIX*encryption` WHERE `uid` = ?' ;
2013-05-04 18:14:38 +04:00
2013-05-27 14:41:55 +04:00
$args = array ( $this -> userId );
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
$query = \OCP\DB :: prepare ( $sql );
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
$result = $query -> execute ( $args );
2013-05-20 03:24:36 +04:00
2013-05-04 18:14:38 +04:00
$migrationStatus = array ();
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
if ( \OCP\DB :: isError ( $result )) {
\OCP\Util :: writeLog ( 'Encryption library' , \OC_DB :: getErrorMessage ( $result ), \OCP\Util :: ERROR );
2013-05-26 01:07:19 +04:00
} else {
2013-06-03 20:42:13 +04:00
if ( $result -> numRows () > 0 ) {
2013-05-27 14:41:55 +04:00
$row = $result -> fetchRow ();
if ( isset ( $row [ 'migration_status' ])) {
$migrationStatus [] = $row [ 'migration_status' ];
}
2013-05-26 01:07:19 +04:00
}
2013-05-04 18:14:38 +04:00
}
2013-05-20 03:24:36 +04:00
2013-05-04 18:14:38 +04:00
// If no record is found
2013-05-27 14:41:55 +04:00
if ( empty ( $migrationStatus )) {
2013-06-11 14:03:50 +04:00
\OCP\Util :: writeLog ( 'Encryption library' , " Could not get migration status for " . $this -> userId . " , no record found " , \OCP\Util :: ERROR );
2013-10-25 17:38:35 +04:00
// insert missing entry in DB with status open
$sql = 'INSERT INTO `*PREFIX*encryption` (`uid`,`mode`,`recovery_enabled`,`migration_status`) VALUES (?,?,?,?)' ;
$args = array (
$this -> userId ,
'server-side' ,
0 ,
self :: MIGRATION_OPEN
);
$query = \OCP\DB :: prepare ( $sql );
$query -> execute ( $args );
2013-10-22 18:15:24 +04:00
return self :: MIGRATION_OPEN ;
2013-05-20 03:24:36 +04:00
// If a record is found
2013-05-04 18:14:38 +04:00
} else {
2013-06-11 15:07:39 +04:00
return ( int ) $migrationStatus [ 0 ];
2013-05-04 18:14:38 +04:00
}
2013-05-20 03:24:36 +04:00
2013-05-04 18:14:38 +04:00
}
2013-05-20 03:24:36 +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-05-20 03:24:36 +04:00
* @ param string $path Path of the file to check
2013-05-30 03:13:22 +04:00
* @ throws \Exception
2013-05-20 03:24:36 +04:00
* @ note $shareFilePath must be relative to data / UID / files . Files
2013-04-10 19:37:03 +04:00
* relative to / Shared are also acceptable
2013-03-26 15:39:55 +04:00
* @ return array
*/
2013-05-27 14:41:55 +04:00
public function getUidAndFilename ( $path ) {
2013-05-20 03:24:36 +04:00
2013-08-22 19:55:10 +04:00
$pathinfo = pathinfo ( $path );
$partfile = false ;
$parentFolder = false ;
2013-08-27 16:19:30 +04:00
if ( array_key_exists ( 'extension' , $pathinfo ) && $pathinfo [ 'extension' ] === 'part' ) {
2013-08-22 19:55:10 +04:00
// if the real file exists we check this file
2013-09-05 12:11:09 +04:00
$filePath = $this -> userFilesDir . '/' . $pathinfo [ 'dirname' ] . '/' . $pathinfo [ 'filename' ];
if ( $this -> view -> file_exists ( $filePath )) {
2013-08-22 19:55:10 +04:00
$pathToCheck = $pathinfo [ 'dirname' ] . '/' . $pathinfo [ 'filename' ];
} else { // otherwise we look for the parent
$pathToCheck = $pathinfo [ 'dirname' ];
$parentFolder = true ;
}
$partfile = true ;
} else {
$pathToCheck = $path ;
}
2013-05-27 14:41:55 +04:00
$view = new \OC\Files\View ( $this -> userFilesDir );
2013-08-22 19:55:10 +04:00
$fileOwnerUid = $view -> getOwner ( $pathToCheck );
2013-04-18 18:37:49 +04:00
2013-05-20 03:24:36 +04:00
// handle public access
2013-05-27 14:41:55 +04:00
if ( $this -> isPublic ) {
2013-05-20 03:24:36 +04:00
$filename = $path ;
$fileOwnerUid = $GLOBALS [ 'fileOwner' ];
2013-04-10 19:37:03 +04:00
2013-05-27 14:41:55 +04:00
return array (
$fileOwnerUid ,
$filename
);
2013-05-20 03:24:36 +04:00
} else {
2013-05-13 23:24:59 +04:00
2013-05-20 03:24:36 +04:00
// Check that UID is valid
2013-05-27 14:41:55 +04:00
if ( ! \OCP\User :: userExists ( $fileOwnerUid )) {
throw new \Exception (
'Could not find owner (UID = "' . var_export ( $fileOwnerUid , 1 ) . '") of file "' . $path . '"' );
2013-05-20 03:24:36 +04:00
}
2013-05-13 23:24:59 +04:00
2013-05-20 03:24:36 +04:00
// NOTE: Bah, this dependency should be elsewhere
2013-05-27 14:41:55 +04:00
\OC\Files\Filesystem :: initMountPoints ( $fileOwnerUid );
2013-05-13 23:24:59 +04:00
2013-05-20 03:24:36 +04:00
// If the file owner is the currently logged in user
2013-05-27 22:44:38 +04:00
if ( $fileOwnerUid === $this -> userId ) {
2013-05-13 23:24:59 +04:00
2013-05-20 03:24:36 +04:00
// Assume the path supplied is correct
$filename = $path ;
2013-05-13 23:24:59 +04:00
2013-05-20 03:24:36 +04:00
} else {
2013-08-22 19:55:10 +04:00
$info = $view -> getFileInfo ( $pathToCheck );
2013-05-27 14:41:55 +04:00
$ownerView = new \OC\Files\View ( '/' . $fileOwnerUid . '/files' );
2013-05-13 23:24:59 +04:00
2013-05-20 03:24:36 +04:00
// Fetch real file path from DB
2013-08-22 19:55:10 +04:00
$filename = $ownerView -> getPath ( $info [ 'fileid' ]);
if ( $parentFolder ) {
$filename = $filename . '/' . $pathinfo [ 'filename' ];
}
if ( $partfile ) {
$filename = $filename . '.' . $pathinfo [ 'extension' ];
}
2013-05-13 23:24:59 +04:00
2013-05-20 03:24:36 +04:00
}
2013-05-13 23:24:59 +04:00
2013-05-27 14:41:55 +04:00
return array (
$fileOwnerUid ,
2013-06-25 18:41:24 +04:00
\OC_Filesystem :: normalizePath ( $filename )
2013-05-27 14:41:55 +04:00
);
2013-05-20 03:24:36 +04:00
}
2013-03-26 15:39:55 +04:00
}
2013-08-22 19:55:10 +04:00
2013-04-22 13:58:39 +04:00
/**
2013-05-24 01:56:31 +04:00
* @ brief go recursively through a dir and collect all files and sub files .
2013-05-20 03:24:36 +04:00
* @ param string $dir relative to the users files folder
2013-04-22 13:58:39 +04:00
* @ return array with list of files relative to the users files folder
*/
2013-05-27 14:41:55 +04:00
public function getAllFiles ( $dir ) {
2013-05-20 03:24:36 +04:00
2013-04-22 13:58:39 +04:00
$result = array ();
2013-05-01 13:04:40 +04:00
2013-06-03 20:42:13 +04:00
$content = $this -> view -> getDirectoryContent ( \OC\Files\Filesystem :: normalizePath (
$this -> userFilesDir . '/' . $dir ));
2013-05-01 13:04:40 +04:00
2013-05-09 16:43:06 +04:00
// handling for re shared folders
2013-05-30 03:13:22 +04:00
$pathSplit = explode ( '/' , $dir );
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
foreach ( $content as $c ) {
2013-05-20 03:24:36 +04:00
2013-05-30 03:13:22 +04:00
$sharedPart = $pathSplit [ sizeof ( $pathSplit ) - 1 ];
2013-05-27 14:41:55 +04:00
$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
2013-05-27 14:41:55 +04:00
foreach ( $targetPathSplit as $pathPart ) {
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
if ( $pathPart !== $sharedPart ) {
2013-05-20 03:24:36 +04:00
2013-05-09 16:43:06 +04:00
$path = '/' . $pathPart . $path ;
2013-05-20 03:24:36 +04:00
2013-05-09 16:43:06 +04:00
} else {
2013-05-20 03:24:36 +04:00
2013-05-09 16:43:06 +04:00
break ;
2013-05-20 03:24:36 +04:00
2013-05-09 16:43:06 +04:00
}
2013-05-20 03:24:36 +04:00
2013-05-09 16:43:06 +04:00
}
2013-05-06 01:41:42 +04:00
2013-05-20 03:24:36 +04:00
$path = $dir . $path ;
2013-05-27 22:44:38 +04:00
if ( $c [ 'type' ] === 'dir' ) {
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
$result = array_merge ( $result , $this -> getAllFiles ( $path ));
2013-05-06 01:41:42 +04:00
2013-04-22 13:58:39 +04:00
} else {
2013-05-20 03:24:36 +04:00
2013-05-09 16:43:06 +04:00
$result [] = $path ;
2013-05-20 03:24:36 +04:00
2013-04-22 13:58:39 +04:00
}
}
2013-05-20 03:24:36 +04:00
2013-04-22 13:58:39 +04:00
return $result ;
2013-05-20 03:24:36 +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
*/
2013-05-27 14:41:55 +04:00
public static function getShareParent ( $id ) {
2013-05-04 05:37:22 +04:00
2013-05-27 14:41:55 +04:00
$sql = 'SELECT `file_target`, `item_type` FROM `*PREFIX*share` WHERE `id` = ?' ;
2013-05-04 05:37:22 +04:00
2013-05-27 14:41:55 +04:00
$query = \OCP\DB :: prepare ( $sql );
$result = $query -> execute ( array ( $id ));
2013-05-04 05:37:22 +04:00
2013-05-26 01:07:19 +04:00
$row = array ();
2013-05-27 14:41:55 +04:00
if ( \OCP\DB :: isError ( $result )) {
\OCP\Util :: writeLog ( 'Encryption library' , \OC_DB :: getErrorMessage ( $result ), \OCP\Util :: ERROR );
2013-05-26 01:07:19 +04:00
} else {
2013-06-03 20:42:13 +04:00
if ( $result -> numRows () > 0 ) {
2013-05-27 14:41:55 +04:00
$row = $result -> fetchRow ();
}
2013-05-25 23:33:05 +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-16 02:34:45 +04:00
/**
* @ brief get shares parent .
* @ param int $id of the current share
* @ return array of the parent
*/
2013-05-27 14:41:55 +04:00
public static function getParentFromShare ( $id ) {
2013-05-16 02:34:45 +04:00
2013-05-27 14:41:55 +04:00
$sql = 'SELECT `parent` FROM `*PREFIX*share` WHERE `id` = ?' ;
2013-05-16 02:34:45 +04:00
2013-05-27 14:41:55 +04:00
$query = \OCP\DB :: prepare ( $sql );
$result = $query -> execute ( array ( $id ));
2013-05-16 02:34:45 +04:00
2013-05-26 01:07:19 +04:00
$row = array ();
2013-05-27 14:41:55 +04:00
if ( \OCP\DB :: isError ( $result )) {
\OCP\Util :: writeLog ( 'Encryption library' , \OC_DB :: getErrorMessage ( $result ), \OCP\Util :: ERROR );
2013-05-26 01:07:19 +04:00
} else {
2013-06-03 20:42:13 +04:00
if ( $result -> numRows () > 0 ) {
2013-05-27 14:41:55 +04:00
$row = $result -> fetchRow ();
}
2013-05-25 23:33:05 +04:00
}
2013-05-16 02:34:45 +04:00
return $row ;
}
2013-05-09 20:16:59 +04:00
/**
* @ brief get owner of the shared files .
2013-05-20 03:24:36 +04:00
* @ param $id
* @ internal param int $Id of a share
* @ return string owner
2013-05-09 20:16:59 +04:00
*/
2013-05-27 14:41:55 +04:00
public function getOwnerFromSharedFile ( $id ) {
2013-05-24 01:56:31 +04:00
2013-05-27 14:41:55 +04:00
$query = \OCP\DB :: prepare ( 'SELECT `parent`, `uid_owner` FROM `*PREFIX*share` WHERE `id` = ?' , 1 );
2013-05-25 23:33:05 +04:00
2013-05-27 14:41:55 +04:00
$result = $query -> execute ( array ( $id ));
2013-05-25 23:33:05 +04:00
2013-05-26 01:07:19 +04:00
$source = array ();
2013-05-27 14:41:55 +04:00
if ( \OCP\DB :: isError ( $result )) {
\OCP\Util :: writeLog ( 'Encryption library' , \OC_DB :: getErrorMessage ( $result ), \OCP\Util :: ERROR );
2013-05-26 01:07:19 +04:00
} else {
2013-06-03 20:42:13 +04:00
if ( $result -> numRows () > 0 ) {
2013-05-27 14:41:55 +04:00
$source = $result -> fetchRow ();
}
2013-05-25 23:33:05 +04:00
}
2013-05-24 01:56:31 +04:00
$fileOwner = false ;
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
if ( isset ( $source [ 'parent' ])) {
2013-05-09 20:16:59 +04:00
$parent = $source [ 'parent' ];
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
while ( isset ( $parent )) {
2013-05-20 03:24:36 +04:00
2013-05-27 14:41:55 +04:00
$query = \OCP\DB :: prepare ( 'SELECT `parent`, `uid_owner` FROM `*PREFIX*share` WHERE `id` = ?' , 1 );
2013-05-25 23:33:05 +04:00
2013-05-27 14:41:55 +04:00
$result = $query -> execute ( array ( $parent ));
2013-05-25 23:33:05 +04:00
2013-05-26 01:07:19 +04:00
$item = array ();
2013-05-27 14:41:55 +04:00
if ( \OCP\DB :: isError ( $result )) {
\OCP\Util :: writeLog ( 'Encryption library' , \OC_DB :: getErrorMessage ( $result ), \OCP\Util :: ERROR );
2013-05-26 01:07:19 +04:00
} else {
2013-06-03 20:42:13 +04:00
if ( $result -> numRows () > 0 ) {
2013-05-27 14:41:55 +04:00
$item = $result -> fetchRow ();
}
2013-05-25 23:33:05 +04:00
}
2013-05-27 14:41:55 +04:00
if ( isset ( $item [ 'parent' ])) {
2013-05-20 03:24:36 +04:00
2013-05-09 20:16:59 +04:00
$parent = $item [ 'parent' ];
2013-05-20 03:24:36 +04:00
2013-05-09 20:16:59 +04:00
} else {
2013-05-20 03:24:36 +04:00
2013-05-09 20:16:59 +04:00
$fileOwner = $item [ 'uid_owner' ];
2013-05-20 03:24:36 +04:00
2013-05-09 20:16:59 +04:00
break ;
2013-05-20 03:24:36 +04:00
2013-05-09 20:16:59 +04:00
}
}
2013-05-20 03:24:36 +04:00
2013-05-09 20:16:59 +04:00
} else {
2013-05-20 03:24:36 +04:00
2013-05-09 20:16:59 +04:00
$fileOwner = $source [ 'uid_owner' ];
2013-05-20 03:24:36 +04:00
2013-05-09 16:43:06 +04:00
}
return $fileOwner ;
2013-05-20 03:24:36 +04:00
2013-05-09 16:43:06 +04:00
}
2013-05-06 01:41:42 +04:00
2013-05-20 03:24:36 +04:00
/**
* @ return string
*/
2013-05-24 01:56:31 +04:00
public function getUserId () {
2013-05-20 03:24:36 +04:00
return $this -> userId ;
}
2013-05-13 23:24:59 +04:00
2013-05-20 03:24:36 +04:00
/**
* @ return string
*/
2013-05-24 01:56:31 +04:00
public function getUserFilesDir () {
2013-05-20 03:24:36 +04:00
return $this -> userFilesDir ;
}
2013-05-13 23:24:59 +04:00
2013-05-20 03:24:36 +04:00
/**
* @ param $password
* @ return bool
*/
2013-05-27 14:41:55 +04:00
public function checkRecoveryPassword ( $password ) {
2013-05-15 18:12:20 +04:00
2013-06-03 16:19:31 +04:00
$result = false ;
2013-05-15 18:12:20 +04:00
$pathKey = '/owncloud_private_key/' . $this -> recoveryKeyId . " .private.key " ;
$proxyStatus = \OC_FileProxy :: $enabled ;
\OC_FileProxy :: $enabled = false ;
2013-05-27 14:41:55 +04:00
$recoveryKey = $this -> view -> file_get_contents ( $pathKey );
2013-05-15 18:12:20 +04:00
2013-06-03 16:19:31 +04:00
$decryptedRecoveryKey = Crypt :: decryptPrivateKey ( $recoveryKey , $password );
2013-05-15 18:12:20 +04:00
2013-06-03 16:19:31 +04:00
if ( $decryptedRecoveryKey ) {
$result = true ;
}
2013-05-15 18:12:20 +04:00
\OC_FileProxy :: $enabled = $proxyStatus ;
2013-05-20 03:24:36 +04:00
2013-06-03 16:19:31 +04:00
return $result ;
2013-05-15 18:12:20 +04:00
}
2013-05-20 03:24:36 +04:00
/**
* @ return string
*/
2013-05-24 01:56:31 +04:00
public function getRecoveryKeyId () {
2013-05-15 18:12:20 +04:00
return $this -> recoveryKeyId ;
}
2013-05-15 19:56:45 +04:00
/**
* @ brief add recovery key to all encrypted files
*/
2013-05-27 14:41:55 +04:00
public function addRecoveryKeys ( $path = '/' ) {
$dirContent = $this -> view -> getDirectoryContent ( $this -> keyfilesPath . $path );
foreach ( $dirContent as $item ) {
2013-05-24 01:56:31 +04:00
// get relative path from files_encryption/keyfiles/
2013-05-27 14:41:55 +04:00
$filePath = substr ( $item [ 'path' ], strlen ( 'files_encryption/keyfiles' ));
2013-05-27 22:44:38 +04:00
if ( $item [ 'type' ] === 'dir' ) {
2013-05-27 14:41:55 +04:00
$this -> addRecoveryKeys ( $filePath . '/' );
2013-05-15 19:56:45 +04:00
} else {
2013-05-28 19:04:35 +04:00
$session = new \OCA\Encryption\Session ( new \OC_FilesystemView ( '/' ));
2013-05-15 19:56:45 +04:00
$sharingEnabled = \OCP\Share :: isEnabled ();
2013-05-29 14:23:33 +04:00
// remove '.key' extension from path e.g. 'file.txt.key' to 'file.txt'
2013-05-27 14:41:55 +04:00
$file = substr ( $filePath , 0 , - 4 );
$usersSharing = $this -> getSharingUsersArray ( $sharingEnabled , $file );
$this -> setSharedFileKeyfiles ( $session , $usersSharing , $file );
2013-05-15 19:56:45 +04:00
}
}
}
2013-05-16 16:53:04 +04:00
/**
2013-05-15 19:56:45 +04:00
* @ brief remove recovery key to all encrypted files
*/
2013-05-27 14:41:55 +04:00
public function removeRecoveryKeys ( $path = '/' ) {
$dirContent = $this -> view -> getDirectoryContent ( $this -> keyfilesPath . $path );
foreach ( $dirContent as $item ) {
2013-05-24 01:56:31 +04:00
// get relative path from files_encryption/keyfiles
2013-05-27 14:41:55 +04:00
$filePath = substr ( $item [ 'path' ], strlen ( 'files_encryption/keyfiles' ));
2013-05-27 22:44:38 +04:00
if ( $item [ 'type' ] === 'dir' ) {
2013-05-27 14:41:55 +04:00
$this -> removeRecoveryKeys ( $filePath . '/' );
2013-05-15 19:56:45 +04:00
} else {
2013-05-29 14:23:33 +04:00
// remove '.key' extension from path e.g. 'file.txt.key' to 'file.txt'
2013-05-27 14:41:55 +04:00
$file = substr ( $filePath , 0 , - 4 );
$this -> view -> unlink ( $this -> shareKeysPath . '/' . $file . '.' . $this -> recoveryKeyId . '.shareKey' );
2013-05-15 19:56:45 +04:00
}
}
}
2013-05-16 16:53:04 +04:00
/**
* @ brief decrypt given file with recovery key and encrypt it again to the owner and his new key
2013-05-20 03:24:36 +04:00
* @ param string $file
* @ param string $privateKey recovery key to decrypt the file
2013-05-16 16:53:04 +04:00
*/
2013-05-27 14:41:55 +04:00
private function recoverFile ( $file , $privateKey ) {
2013-05-16 16:53:04 +04:00
$sharingEnabled = \OCP\Share :: isEnabled ();
// Find out who, if anyone, is sharing the file
2013-05-27 14:41:55 +04:00
if ( $sharingEnabled ) {
2013-05-29 15:10:26 +04:00
$result = \OCP\Share :: getUsersSharingFile ( $file , $this -> userId , true );
2013-05-16 16:53:04 +04:00
$userIds = $result [ 'users' ];
$userIds [] = $this -> recoveryKeyId ;
2013-05-27 14:41:55 +04:00
if ( $result [ 'public' ]) {
2013-05-16 16:53:04 +04:00
$userIds [] = $this -> publicShareKeyId ;
}
} else {
2013-05-27 14:41:55 +04:00
$userIds = array (
$this -> userId ,
$this -> recoveryKeyId
);
2013-05-16 16:53:04 +04:00
}
2013-05-27 14:41:55 +04:00
$filteredUids = $this -> filterShareReadyUsers ( $userIds );
2013-05-16 16:53:04 +04:00
$proxyStatus = \OC_FileProxy :: $enabled ;
\OC_FileProxy :: $enabled = false ;
//decrypt file key
2013-05-27 14:41:55 +04:00
$encKeyfile = $this -> view -> file_get_contents ( $this -> keyfilesPath . $file . " .key " );
$shareKey = $this -> view -> file_get_contents (
$this -> shareKeysPath . $file . " . " . $this -> recoveryKeyId . " .shareKey " );
$plainKeyfile = Crypt :: multiKeyDecrypt ( $encKeyfile , $shareKey , $privateKey );
2013-05-16 16:53:04 +04:00
// encrypt file key again to all users, this time with the new public key for the recovered use
2013-05-27 14:41:55 +04:00
$userPubKeys = Keymanager :: getPublicKeys ( $this -> view , $filteredUids [ 'ready' ]);
$multiEncKey = Crypt :: multiKeyEncrypt ( $plainKeyfile , $userPubKeys );
2013-05-16 16:53:04 +04:00
// write new keys to filesystem TDOO!
2013-05-27 14:41:55 +04:00
$this -> view -> file_put_contents ( $this -> keyfilesPath . $file . '.key' , $multiEncKey [ 'data' ]);
foreach ( $multiEncKey [ 'keys' ] as $userId => $shareKey ) {
2013-05-20 03:24:36 +04:00
$shareKeyPath = $this -> shareKeysPath . $file . '.' . $userId . '.shareKey' ;
2013-05-27 14:41:55 +04:00
$this -> view -> file_put_contents ( $shareKeyPath , $shareKey );
2013-05-16 16:53:04 +04:00
}
// Return proxy to original status
\OC_FileProxy :: $enabled = $proxyStatus ;
}
/**
* @ brief collect all files and recover them one by one
2013-05-20 03:24:36 +04:00
* @ param string $path to look for files keys
* @ param string $privateKey private recovery key which is used to decrypt the files
2013-05-16 16:53:04 +04:00
*/
2013-05-27 14:41:55 +04:00
private function recoverAllFiles ( $path , $privateKey ) {
$dirContent = $this -> view -> getDirectoryContent ( $this -> keyfilesPath . $path );
foreach ( $dirContent as $item ) {
2013-05-29 14:23:33 +04:00
// get relative path from files_encryption/keyfiles
$filePath = substr ( $item [ 'path' ], strlen ( 'files_encryption/keyfiles' ));
2013-05-27 22:44:38 +04:00
if ( $item [ 'type' ] === 'dir' ) {
2013-05-27 14:41:55 +04:00
$this -> recoverAllFiles ( $filePath . '/' , $privateKey );
2013-05-16 16:53:04 +04:00
} else {
2013-05-29 14:23:33 +04:00
// remove '.key' extension from path e.g. 'file.txt.key' to 'file.txt'
2013-05-27 14:41:55 +04:00
$file = substr ( $filePath , 0 , - 4 );
$this -> recoverFile ( $file , $privateKey );
2013-05-16 16:53:04 +04:00
}
}
}
/**
* @ brief recover users files in case of password lost
2013-05-20 03:24:36 +04:00
* @ param string $recoveryPassword
2013-05-16 16:53:04 +04:00
*/
2013-05-27 14:41:55 +04:00
public function recoverUsersFiles ( $recoveryPassword ) {
2013-05-16 16:53:04 +04:00
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy :: $enabled ;
\OC_FileProxy :: $enabled = false ;
2013-05-27 14:41:55 +04:00
$encryptedKey = $this -> view -> file_get_contents (
'/owncloud_private_key/' . $this -> recoveryKeyId . '.private.key' );
2013-06-03 16:19:31 +04:00
$privateKey = Crypt :: decryptPrivateKey ( $encryptedKey , $recoveryPassword );
2013-05-16 16:53:04 +04:00
\OC_FileProxy :: $enabled = $proxyStatus ;
2013-05-27 14:41:55 +04:00
$this -> recoverAllFiles ( '/' , $privateKey );
2013-05-16 16:53:04 +04:00
}
2013-05-23 22:30:07 +04:00
2013-05-29 21:11:39 +04:00
/**
* Get the path including the storage mount point
* @ param int $id
* @ return string the path including the mount point like AmazonS3 / folder / file . txt
*/
public function getPathWithMountPoint ( $id ) {
list ( $storage , $internalPath ) = \OC\Files\Cache\Cache :: getById ( $id );
$mount = \OC\Files\Filesystem :: getMountByStorageId ( $storage );
$mountPoint = $mount [ 0 ] -> getMountPoint ();
2013-06-03 20:42:13 +04:00
$path = \OC\Files\Filesystem :: normalizePath ( $mountPoint . '/' . $internalPath );
2013-05-29 21:11:39 +04:00
// reformat the path to be relative e.g. /user/files/folder becomes /folder/
2013-05-30 03:13:22 +04:00
$relativePath = \OCA\Encryption\Helper :: stripUserFilesPath ( $path );
2013-05-29 21:11:39 +04:00
return $relativePath ;
}
2013-06-25 16:25:00 +04:00
/**
* @ brief check if the file is stored on a system wide mount point
* @ param $path relative to / data / user with leading '/'
* @ return boolean
*/
public function isSystemWideMountPoint ( $path ) {
2013-06-25 19:46:04 +04:00
if ( \OCP\App :: isEnabled ( " files_external " )) {
$mount = \OC_Mount_Config :: getSystemMountPoints ();
foreach ( $mount as $mountPoint => $data ) {
if ( $mountPoint == substr ( $path , 1 , strlen ( $mountPoint ))) {
return true ;
}
2013-06-25 16:25:00 +04:00
}
}
return false ;
}
2013-07-29 19:06:05 +04:00
/**
* @ brief decrypt private key and add it to the current session
* @ param array $params with 'uid' and 'password'
* @ return mixed session or false
*/
public function initEncryption ( $params ) {
2013-09-02 13:26:11 +04:00
$session = new \OCA\Encryption\Session ( $this -> view );
// we tried to initialize the encryption app for this session
2013-09-06 14:27:40 +04:00
$session -> setInitialized ( \OCA\Encryption\Session :: INIT_EXECUTED );
2013-09-02 13:26:11 +04:00
2013-07-29 19:06:05 +04:00
$encryptedKey = Keymanager :: getPrivateKey ( $this -> view , $params [ 'uid' ]);
$privateKey = Crypt :: decryptPrivateKey ( $encryptedKey , $params [ 'password' ]);
if ( $privateKey === false ) {
\OCP\Util :: writeLog ( 'Encryption library' , 'Private key for user "' . $params [ 'uid' ]
. '" is not valid! Maybe the user password was changed from outside if so please change it back to gain access' , \OCP\Util :: ERROR );
return false ;
}
$session -> setPrivateKey ( $privateKey );
2013-09-06 14:27:40 +04:00
$session -> setInitialized ( \OCA\Encryption\Session :: INIT_SUCCESSFUL );
2013-08-18 13:21:01 +04:00
2013-07-29 19:06:05 +04:00
return $session ;
}
2013-08-18 13:21:01 +04:00
2012-07-11 20:51:27 +04:00
}