2021-06-23 10:46:01 -04:00
< ? php
/**
* @ author Sujith Haridasan < sharidasan @ owncloud . com >
* @ author Ilja Neumann < ineumann @ owncloud . com >
*
* @ copyright Copyright ( c ) 2019 , ownCloud GmbH
* @ license AGPL - 3.0
*
* This code is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License , version 3 ,
* as published by the Free Software Foundation .
*
* This program 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 , version 3 ,
* along with this program . If not , see < http :// www . gnu . org / licenses />
*
*/
namespace OCA\Encryption\Command ;
use OC\Files\View ;
2021-08-11 03:18:16 -04:00
use OC\ServerNotAvailableException ;
2021-06-29 14:44:07 -04:00
use OCA\Encryption\Util ;
2021-06-23 10:46:01 -04:00
use OCP\Files\IRootFolder ;
2021-06-29 19:20:33 -04:00
use OCP\HintException ;
2021-06-24 03:31:52 -04:00
use OCP\IConfig ;
2021-06-24 04:34:55 -04:00
use OCP\ILogger ;
2021-06-23 10:46:01 -04:00
use OCP\IUserManager ;
use Symfony\Component\Console\Command\Command ;
use Symfony\Component\Console\Input\InputArgument ;
use Symfony\Component\Console\Input\InputInterface ;
use Symfony\Component\Console\Output\OutputInterface ;
class FixEncryptedVersion extends Command {
2021-06-24 03:31:52 -04:00
/** @var IConfig */
private $config ;
2021-06-24 04:34:55 -04:00
/** @var ILogger */
private $logger ;
2021-06-23 10:46:01 -04:00
/** @var IRootFolder */
private $rootFolder ;
/** @var IUserManager */
private $userManager ;
2021-06-29 14:44:07 -04:00
/** @var Util */
private $util ;
2021-06-23 10:46:01 -04:00
/** @var View */
private $view ;
2021-08-11 03:18:16 -04:00
/** @var bool */
private $supportLegacy ;
2021-06-29 14:44:07 -04:00
public function __construct (
IConfig $config ,
ILogger $logger ,
IRootFolder $rootFolder ,
IUserManager $userManager ,
Util $util ,
View $view
) {
2021-06-24 03:31:52 -04:00
$this -> config = $config ;
2021-06-24 04:34:55 -04:00
$this -> logger = $logger ;
2021-06-23 10:46:01 -04:00
$this -> rootFolder = $rootFolder ;
$this -> userManager = $userManager ;
2021-06-29 14:44:07 -04:00
$this -> util = $util ;
2021-06-23 10:46:01 -04:00
$this -> view = $view ;
2021-08-11 03:18:16 -04:00
$this -> supportLegacy = false ;
2021-06-23 10:46:01 -04:00
parent :: __construct ();
}
2021-06-24 04:34:55 -04:00
protected function configure () : void {
2021-06-23 10:46:01 -04:00
parent :: configure ();
$this
-> setName ( 'encryption:fix-encrypted-version' )
-> setDescription ( 'Fix the encrypted version if the encrypted file(s) are not downloadable.' )
-> addArgument (
'user' ,
InputArgument :: REQUIRED ,
'The id of the user whose files need fixing'
) -> addOption (
'path' ,
'p' ,
InputArgument :: OPTIONAL ,
'Limit files to fix with path, e.g., --path="/Music/Artist". If path indicates a directory, all the files inside directory will be fixed.'
);
}
/**
* @ param InputInterface $input
* @ param OutputInterface $output
* @ return int
*/
2021-06-24 04:34:55 -04:00
protected function execute ( InputInterface $input , OutputInterface $output ) : int {
2021-06-24 03:31:52 -04:00
$skipSignatureCheck = $this -> config -> getSystemValue ( 'encryption_skip_signature_check' , false );
2021-08-11 03:18:16 -04:00
$this -> supportLegacy = $this -> config -> getSystemValueBool ( 'encryption.legacy_format_support' , false );
2021-06-24 03:31:52 -04:00
if ( $skipSignatureCheck ) {
$output -> writeln ( " <error>Repairing is not possible when \" encryption_skip_signature_check \" is set. Please disable this flag in the configuration.</error> \n " );
return 1 ;
}
2021-06-29 14:44:07 -04:00
if ( ! $this -> util -> isMasterKeyEnabled ()) {
$output -> writeln ( " <error>Repairing only works with master key encryption.</error> \n " );
return 1 ;
}
2021-06-24 04:34:55 -04:00
$user = ( string ) $input -> getArgument ( 'user' );
2021-06-23 10:46:01 -04:00
$pathToWalk = " / $user /files " ;
/**
* trim () returns an empty string when the argument is an unset / null
*/
$pathOption = \trim ( $input -> getOption ( 'path' ), '/' );
if ( $pathOption !== " " ) {
$pathToWalk = " $pathToWalk / $pathOption " ;
}
if ( $user === null ) {
$output -> writeln ( " <error>No user id provided.</error> \n " );
return 1 ;
}
if ( $this -> userManager -> get ( $user ) === null ) {
$output -> writeln ( " <error>User id $user does not exist. Please provide a valid user id</error> " );
return 1 ;
}
return $this -> walkPathOfUser ( $user , $pathToWalk , $output );
}
/**
* @ param string $user
* @ param string $path
* @ param OutputInterface $output
* @ return int 0 for success , 1 for error
*/
2021-06-24 04:34:55 -04:00
private function walkPathOfUser ( $user , $path , OutputInterface $output ) : int {
2021-06-23 10:46:01 -04:00
$this -> setupUserFs ( $user );
if ( ! $this -> view -> file_exists ( $path )) {
2021-06-24 04:51:07 -04:00
$output -> writeln ( " <error>Path \" $path\ " does not exist . Please provide a valid path .</ error > " );
2021-06-23 10:46:01 -04:00
return 1 ;
}
if ( $this -> view -> is_file ( $path )) {
2021-06-24 04:51:07 -04:00
$output -> writeln ( " Verifying the content of file \" $path\ " " );
2021-06-23 10:46:01 -04:00
$this -> verifyFileContent ( $path , $output );
return 0 ;
}
$directories = [];
$directories [] = $path ;
while ( $root = \array_pop ( $directories )) {
$directoryContent = $this -> view -> getDirectoryContent ( $root );
foreach ( $directoryContent as $file ) {
$path = $root . '/' . $file [ 'name' ];
if ( $this -> view -> is_dir ( $path )) {
$directories [] = $path ;
} else {
2021-06-24 04:51:07 -04:00
$output -> writeln ( " Verifying the content of file \" $path\ " " );
2021-06-23 10:46:01 -04:00
$this -> verifyFileContent ( $path , $output );
}
}
}
return 0 ;
}
/**
* @ param string $path
* @ param OutputInterface $output
* @ param bool $ignoreCorrectEncVersionCall , setting this variable to false avoids recursion
*/
2021-06-24 04:34:55 -04:00
private function verifyFileContent ( $path , OutputInterface $output , $ignoreCorrectEncVersionCall = true ) : bool {
2021-06-23 10:46:01 -04:00
try {
/**
* In encryption , the files are read in a block size of 8192 bytes
* Read block size of 8192 and a bit more ( 808 bytes )
* If there is any problem , the first block should throw the signature
* mismatch error . Which as of now , is enough to proceed ahead to
* correct the encrypted version .
*/
$handle = $this -> view -> fopen ( $path , 'rb' );
if ( \fread ( $handle , 9001 ) !== false ) {
2021-06-24 04:51:07 -04:00
$output -> writeln ( " <info>The file \" $path\ " is : OK </ info > " );
2021-06-23 10:46:01 -04:00
}
\fclose ( $handle );
return true ;
2021-08-11 03:18:16 -04:00
} catch ( ServerNotAvailableException $e ) {
// not a "bad signature" error and likely "legacy cipher" exception
// this could mean that the file is maybe not encrypted but the encrypted version is set
if ( ! $this -> supportLegacy && $ignoreCorrectEncVersionCall === true ) {
$output -> writeln ( " <info>Attempting to fix the path: \" $path\ " </ info > " );
return $this -> correctEncryptedVersion ( $path , $output , true );
}
return false ;
2021-06-23 10:46:01 -04:00
} catch ( HintException $e ) {
2021-06-24 04:34:55 -04:00
$this -> logger -> warning ( " Issue: " . $e -> getMessage ());
2021-06-23 10:46:01 -04:00
//If allowOnce is set to false, this becomes recursive.
if ( $ignoreCorrectEncVersionCall === true ) {
//Lets rectify the file by correcting encrypted version
2021-06-24 04:51:07 -04:00
$output -> writeln ( " <info>Attempting to fix the path: \" $path\ " </ info > " );
2021-06-23 10:46:01 -04:00
return $this -> correctEncryptedVersion ( $path , $output );
}
return false ;
}
}
/**
* @ param string $path
* @ param OutputInterface $output
2021-08-11 03:18:16 -04:00
* @ param bool $includeZero whether to try zero version for unencrypted file
2021-06-23 10:46:01 -04:00
* @ return bool
*/
2021-08-11 03:18:16 -04:00
private function correctEncryptedVersion ( $path , OutputInterface $output , bool $includeZero = false ) : bool {
2021-06-23 10:46:01 -04:00
$fileInfo = $this -> view -> getFileInfo ( $path );
2021-06-24 04:34:55 -04:00
if ( ! $fileInfo ) {
$output -> writeln ( " <warning>File info not found for file: \" $path\ " </ warning > " );
return true ;
}
2021-06-23 10:46:01 -04:00
$fileId = $fileInfo -> getId ();
$encryptedVersion = $fileInfo -> getEncryptedVersion ();
$wrongEncryptedVersion = $encryptedVersion ;
$storage = $fileInfo -> getStorage ();
$cache = $storage -> getCache ();
$fileCache = $cache -> get ( $fileId );
2021-06-24 04:34:55 -04:00
if ( ! $fileCache ) {
$output -> writeln ( " <warning>File cache entry not found for file: \" $path\ " </ warning > " );
return true ;
}
2021-06-23 10:46:01 -04:00
if ( $storage -> instanceOfStorage ( 'OCA\Files_Sharing\ISharedStorage' )) {
2021-06-24 04:34:55 -04:00
$output -> writeln ( " <info>The file: \" $path\ " is a share . Please also run the script for the owner of the share </ info > " );
2021-06-23 10:46:01 -04:00
return true ;
}
// Save original encrypted version so we can restore it if decryption fails with all version
$originalEncryptedVersion = $encryptedVersion ;
if ( $encryptedVersion >= 0 ) {
2021-08-11 03:18:16 -04:00
if ( $includeZero ) {
// try with zero first
$cacheInfo = [ 'encryptedVersion' => 0 , 'encrypted' => 0 ];
$cache -> put ( $fileCache -> getPath (), $cacheInfo );
$output -> writeln ( " <info>Set the encrypted version to 0 (unencrypted)</info> " );
if ( $this -> verifyFileContent ( $path , $output , false ) === true ) {
$output -> writeln ( " <info>Fixed the file: \" $path\ " with version 0 ( unencrypted ) </ info > " );
return true ;
}
}
2021-06-23 10:46:01 -04:00
//test by decrementing the value till 1 and if nothing works try incrementing
$encryptedVersion -- ;
while ( $encryptedVersion > 0 ) {
$cacheInfo = [ 'encryptedVersion' => $encryptedVersion , 'encrypted' => $encryptedVersion ];
$cache -> put ( $fileCache -> getPath (), $cacheInfo );
$output -> writeln ( " <info>Decrement the encrypted version to $encryptedVersion </info> " );
if ( $this -> verifyFileContent ( $path , $output , false ) === true ) {
2021-06-24 04:34:55 -04:00
$output -> writeln ( " <info>Fixed the file: \" $path\ " with version " . $encryptedVersion . " </ info > " );
2021-06-23 10:46:01 -04:00
return true ;
}
$encryptedVersion -- ;
}
//So decrementing did not work. Now lets increment. Max increment is till 5
$increment = 1 ;
while ( $increment <= 5 ) {
/**
* The wrongEncryptedVersion would not be incremented so nothing to worry about here .
* Only the newEncryptedVersion is incremented .
* For example if the wrong encrypted version is 4 then
* cycle1 -> newEncryptedVersion = 5 ( 4 + 1 )
* cycle2 -> newEncryptedVersion = 6 ( 4 + 2 )
* cycle3 -> newEncryptedVersion = 7 ( 4 + 3 )
*/
$newEncryptedVersion = $wrongEncryptedVersion + $increment ;
$cacheInfo = [ 'encryptedVersion' => $newEncryptedVersion , 'encrypted' => $newEncryptedVersion ];
$cache -> put ( $fileCache -> getPath (), $cacheInfo );
$output -> writeln ( " <info>Increment the encrypted version to $newEncryptedVersion </info> " );
if ( $this -> verifyFileContent ( $path , $output , false ) === true ) {
2021-06-24 04:34:55 -04:00
$output -> writeln ( " <info>Fixed the file: \" $path\ " with version " . $newEncryptedVersion . " </ info > " );
2021-06-23 10:46:01 -04:00
return true ;
}
$increment ++ ;
}
}
$cacheInfo = [ 'encryptedVersion' => $originalEncryptedVersion , 'encrypted' => $originalEncryptedVersion ];
$cache -> put ( $fileCache -> getPath (), $cacheInfo );
2021-06-24 04:34:55 -04:00
$output -> writeln ( " <info>No fix found for \" $path\ " , restored version to original : $originalEncryptedVersion </ info > " );
2021-06-23 10:46:01 -04:00
return false ;
}
/**
* Setup user file system
* @ param string $uid
*/
2021-06-24 04:34:55 -04:00
private function setupUserFs ( $uid ) : void {
2021-06-23 10:46:01 -04:00
\OC_Util :: tearDownFS ();
\OC_Util :: setupFS ( $uid );
}
}