From dddab717bf3bf593cfdd6f3d88e315a89b144d34 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 20 Oct 2021 22:39:13 +0200 Subject: [PATCH 1/7] add KerberosApacheAuth support to files_external Signed-off-by: Arthur Schiwon --- .../lib/AppInfo/Application.php | 6 ++- .../lib/Lib/Auth/SMB/KerberosApacheAuth.php | 46 +++++++++++++++++++ apps/files_external/lib/Lib/Backend/SMB.php | 33 +++++++++++-- 3 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 apps/files_external/lib/Lib/Auth/SMB/KerberosApacheAuth.php diff --git a/apps/files_external/lib/AppInfo/Application.php b/apps/files_external/lib/AppInfo/Application.php index 7f6d886335064..222116db5eccb 100644 --- a/apps/files_external/lib/AppInfo/Application.php +++ b/apps/files_external/lib/AppInfo/Application.php @@ -31,8 +31,6 @@ use OCA\Files_External\Config\ConfigAdapter; use OCA\Files_External\Config\UserPlaceholderHandler; -use OCA\Files_External\Listener\GroupDeletedListener; -use OCA\Files_External\Listener\UserDeletedListener; use OCA\Files_External\Lib\Auth\AmazonS3\AccessKey; use OCA\Files_External\Lib\Auth\Builtin; use OCA\Files_External\Lib\Auth\NullMechanism; @@ -49,6 +47,7 @@ use OCA\Files_External\Lib\Auth\Password\UserProvided; use OCA\Files_External\Lib\Auth\PublicKey\RSA; use OCA\Files_External\Lib\Auth\PublicKey\RSAPrivateKey; +use OCA\Files_External\Lib\Auth\SMB\KerberosApacheAuth; use OCA\Files_External\Lib\Auth\SMB\KerberosAuth; use OCA\Files_External\Lib\Backend\AmazonS3; use OCA\Files_External\Lib\Backend\DAV; @@ -62,6 +61,8 @@ use OCA\Files_External\Lib\Backend\Swift; use OCA\Files_External\Lib\Config\IAuthMechanismProvider; use OCA\Files_External\Lib\Config\IBackendProvider; +use OCA\Files_External\Listener\GroupDeletedListener; +use OCA\Files_External\Listener\UserDeletedListener; use OCA\Files_External\Service\BackendService; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; @@ -180,6 +181,7 @@ public function getAuthMechanisms() { // Specialized mechanisms $container->query(AccessKey::class), $container->query(KerberosAuth::class), + $container->query(KerberosApacheAuth::class), ]; } } diff --git a/apps/files_external/lib/Lib/Auth/SMB/KerberosApacheAuth.php b/apps/files_external/lib/Lib/Auth/SMB/KerberosApacheAuth.php new file mode 100644 index 0000000000000..645038102256d --- /dev/null +++ b/apps/files_external/lib/Lib/Auth/SMB/KerberosApacheAuth.php @@ -0,0 +1,46 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace OCA\Files_External\Lib\Auth\SMB; + +use OCA\Files_External\Lib\Auth\AuthMechanism; +use OCP\Authentication\LoginCredentials\IStore; +use OCP\IL10N; + +class KerberosApacheAuth extends AuthMechanism { + /** @var IStore */ + private $credentialsStore; + + public function __construct(IL10N $l, IStore $credentialsStore) { + $this + ->setIdentifier('smb::kerberosapache') + ->setScheme(self::SCHEME_SMB) + ->setText($l->t('Kerberos ticket apache mode')); + $this->credentialsStore = $credentialsStore; + } + + public function getCredentialsStore(): IStore { + return $this->credentialsStore; + } +} diff --git a/apps/files_external/lib/Lib/Backend/SMB.php b/apps/files_external/lib/Lib/Backend/SMB.php index 867648824ac1d..99e48b1433d08 100644 --- a/apps/files_external/lib/Lib/Backend/SMB.php +++ b/apps/files_external/lib/Lib/Backend/SMB.php @@ -24,16 +24,18 @@ * along with this program. If not, see * */ + namespace OCA\Files_External\Lib\Backend; use Icewind\SMB\BasicAuth; +use Icewind\SMB\KerberosApacheAuth; use Icewind\SMB\KerberosAuth; use OCA\Files_External\Lib\Auth\AuthMechanism; use OCA\Files_External\Lib\Auth\Password\Password; use OCA\Files_External\Lib\DefinitionParameter; +use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; use OCA\Files_External\Lib\LegacyDependencyCheckPolyfill; use OCA\Files_External\Lib\StorageConfig; - use OCP\IL10N; use OCP\IUser; @@ -69,10 +71,6 @@ public function __construct(IL10N $l, Password $legacyAuth) { ->setLegacyAuthMechanism($legacyAuth); } - /** - * @param StorageConfig $storage - * @param IUser $user - */ public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) { $auth = $storage->getAuthMechanism(); if ($auth->getScheme() === AuthMechanism::SCHEME_PASSWORD) { @@ -89,6 +87,31 @@ public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = n switch ($auth->getIdentifier()) { case 'smb::kerberos': $smbAuth = new KerberosAuth(); + break; + case 'smb::kerberosapache': + $credentialsStore = $auth->getCredentialsStore(); + $kerb_auth = new KerberosApacheAuth(); + if ($kerb_auth->checkTicket()) { + $kerb_auth->registerApacheKerberosTicket(); + $smbAuth = $kerb_auth; + } else { + try { + $credentials = $credentialsStore->getLoginCredentials(); + $user = $credentials->getLoginName(); + $pass = $credentials->getPassword(); + if (preg_match('/(.*)@(.*)/', $user, $matches) !== 1) { + throw new InsufficientDataForMeaningfulAnswerException('No valid session credentials'); + } + $smbAuth = new BasicAuth( + $matches[0], + $matches[1], + $pass + ); + } catch (\Exception $e) { + throw new InsufficientDataForMeaningfulAnswerException('No session credentials saved'); + } + } + break; default: throw new \InvalidArgumentException('unknown authentication backend'); From 8245c8158442589696e5dcf3805c937e65953389 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 29 Oct 2021 18:27:30 +0200 Subject: [PATCH 2/7] add changes from Sebastian/dassIT and move default_realm to backend - Sebastian added the switch depending on the preg_match result and with it the fall back to login credentials - I turned default_realm to a backend option (was previously suggested as system config key) Signed-off-by: Arthur Schiwon --- .../lib/Lib/Auth/SMB/KerberosApacheAuth.php | 9 +++++++- apps/files_external/lib/Lib/Backend/SMB.php | 23 +++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/apps/files_external/lib/Lib/Auth/SMB/KerberosApacheAuth.php b/apps/files_external/lib/Lib/Auth/SMB/KerberosApacheAuth.php index 645038102256d..88aaa417a87e3 100644 --- a/apps/files_external/lib/Lib/Auth/SMB/KerberosApacheAuth.php +++ b/apps/files_external/lib/Lib/Auth/SMB/KerberosApacheAuth.php @@ -25,6 +25,7 @@ namespace OCA\Files_External\Lib\Auth\SMB; use OCA\Files_External\Lib\Auth\AuthMechanism; +use OCA\Files_External\Lib\DefinitionParameter; use OCP\Authentication\LoginCredentials\IStore; use OCP\IL10N; @@ -33,10 +34,16 @@ class KerberosApacheAuth extends AuthMechanism { private $credentialsStore; public function __construct(IL10N $l, IStore $credentialsStore) { + $realm = new DefinitionParameter('default_realm', 'Default realm'); + $realm + ->setType(DefinitionParameter::VALUE_TEXT) + ->setFlag(DefinitionParameter::FLAG_OPTIONAL) + ->setTooltip($l->t('Kerberos default realm, defaults to "WORKGROUP"')); $this ->setIdentifier('smb::kerberosapache') ->setScheme(self::SCHEME_SMB) - ->setText($l->t('Kerberos ticket apache mode')); + ->setText($l->t('Kerberos ticket apache mode')) + ->addParameter($realm); $this->credentialsStore = $credentialsStore; } diff --git a/apps/files_external/lib/Lib/Backend/SMB.php b/apps/files_external/lib/Lib/Backend/SMB.php index 99e48b1433d08..b6854e6938d19 100644 --- a/apps/files_external/lib/Lib/Backend/SMB.php +++ b/apps/files_external/lib/Lib/Backend/SMB.php @@ -32,6 +32,7 @@ use Icewind\SMB\KerberosAuth; use OCA\Files_External\Lib\Auth\AuthMechanism; use OCA\Files_External\Lib\Auth\Password\Password; +use OCA\Files_External\Lib\Auth\SMB\KerberosApacheAuth as KerberosApacheAuthMechanism; use OCA\Files_External\Lib\DefinitionParameter; use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; use OCA\Files_External\Lib\LegacyDependencyCheckPolyfill; @@ -89,6 +90,9 @@ public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = n $smbAuth = new KerberosAuth(); break; case 'smb::kerberosapache': + if (!$auth instanceof KerberosApacheAuthMechanism) { + throw new \InvalidArgumentException('invalid authentication backend'); + } $credentialsStore = $auth->getCredentialsStore(); $kerb_auth = new KerberosApacheAuth(); if ($kerb_auth->checkTicket()) { @@ -99,12 +103,23 @@ public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = n $credentials = $credentialsStore->getLoginCredentials(); $user = $credentials->getLoginName(); $pass = $credentials->getPassword(); - if (preg_match('/(.*)@(.*)/', $user, $matches) !== 1) { - throw new InsufficientDataForMeaningfulAnswerException('No valid session credentials'); + preg_match('/(.*)@(.*)/', $user, $matches); + $realm = $storage->getBackendOption('default_realm'); + if (empty($realm)) { + $realm = 'WORKGROUP'; + } + $userPart = $matches[1]; + $domainPart = $matches[2]; + if (count($matches) === 0) { + $username = $user; + $workgroup = $realm; + } else { + $username = $userPart; + $workgroup = $domainPart; } $smbAuth = new BasicAuth( - $matches[0], - $matches[1], + $username, + $workgroup, $pass ); } catch (\Exception $e) { From efad8ab4e3a6e30952c455834b37d60f46aea664 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 3 Nov 2021 13:07:00 +0100 Subject: [PATCH 3/7] do not use deprecated query() method Signed-off-by: Arthur Schiwon --- .../lib/AppInfo/Application.php | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/apps/files_external/lib/AppInfo/Application.php b/apps/files_external/lib/AppInfo/Application.php index 222116db5eccb..6f8018746b34b 100644 --- a/apps/files_external/lib/AppInfo/Application.php +++ b/apps/files_external/lib/AppInfo/Application.php @@ -127,16 +127,16 @@ public function getBackends() { $container = $this->getContainer(); $backends = [ - $container->query(Local::class), - $container->query(FTP::class), - $container->query(DAV::class), - $container->query(OwnCloud::class), - $container->query(SFTP::class), - $container->query(AmazonS3::class), - $container->query(Swift::class), - $container->query(SFTP_Key::class), - $container->query(SMB::class), - $container->query(SMB_OC::class), + $container->get(Local::class), + $container->get(FTP::class), + $container->get(DAV::class), + $container->get(OwnCloud::class), + $container->get(SFTP::class), + $container->get(AmazonS3::class), + $container->get(Swift::class), + $container->get(SFTP_Key::class), + $container->get(SMB::class), + $container->get(SMB_OC::class), ]; return $backends; @@ -150,38 +150,38 @@ public function getAuthMechanisms() { return [ // AuthMechanism::SCHEME_NULL mechanism - $container->query(NullMechanism::class), + $container->get(NullMechanism::class), // AuthMechanism::SCHEME_BUILTIN mechanism - $container->query(Builtin::class), + $container->get(Builtin::class), // AuthMechanism::SCHEME_PASSWORD mechanisms - $container->query(Password::class), - $container->query(SessionCredentials::class), - $container->query(LoginCredentials::class), - $container->query(UserProvided::class), - $container->query(GlobalAuth::class), - $container->query(UserGlobalAuth::class), + $container->get(Password::class), + $container->get(SessionCredentials::class), + $container->get(LoginCredentials::class), + $container->get(UserProvided::class), + $container->get(GlobalAuth::class), + $container->get(UserGlobalAuth::class), // AuthMechanism::SCHEME_OAUTH1 mechanisms - $container->query(OAuth1::class), + $container->get(OAuth1::class), // AuthMechanism::SCHEME_OAUTH2 mechanisms - $container->query(OAuth2::class), + $container->get(OAuth2::class), // AuthMechanism::SCHEME_PUBLICKEY mechanisms - $container->query(RSA::class), - $container->query(RSAPrivateKey::class), + $container->get(RSA::class), + $container->get(RSAPrivateKey::class), // AuthMechanism::SCHEME_OPENSTACK mechanisms - $container->query(OpenStackV2::class), - $container->query(OpenStackV3::class), - $container->query(Rackspace::class), + $container->get(OpenStackV2::class), + $container->get(OpenStackV3::class), + $container->get(Rackspace::class), // Specialized mechanisms - $container->query(AccessKey::class), - $container->query(KerberosAuth::class), - $container->query(KerberosApacheAuth::class), + $container->get(AccessKey::class), + $container->get(KerberosAuth::class), + $container->get(KerberosApacheAuth::class), ]; } } From 39b294e0f80980b76178a9a45528b5ce8e38f096 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 4 Nov 2021 15:05:37 +0100 Subject: [PATCH 4/7] update icewind/smb to 3.5.1 Signed-off-by: Robin Appelman --- apps/files_external/3rdparty/.gitignore | 2 + apps/files_external/3rdparty/composer.json | 2 +- apps/files_external/3rdparty/composer.lock | 14 +-- .../3rdparty/composer/ClassLoader.php | 113 +++++++++++++++-- .../3rdparty/composer/InstalledVersions.php | 25 +++- .../3rdparty/composer/autoload_classmap.php | 1 + .../3rdparty/composer/autoload_static.php | 1 + .../3rdparty/composer/installed.json | 14 +-- .../3rdparty/composer/installed.php | 10 +- .../3rdparty/icewind/smb/README.md | 31 ++++- .../3rdparty/icewind/smb/src/IShare.php | 2 +- .../icewind/smb/src/KerberosApacheAuth.php | 117 ++++++++++++++++++ .../icewind/smb/src/Native/NativeFileInfo.php | 2 +- .../icewind/smb/src/Native/NativeShare.php | 12 +- .../icewind/smb/src/Native/NativeState.php | 13 ++ .../3rdparty/icewind/smb/src/System.php | 2 +- .../icewind/smb/src/Wrapped/Connection.php | 43 ++----- .../icewind/smb/src/Wrapped/NotifyHandler.php | 14 ++- .../icewind/smb/src/Wrapped/RawConnection.php | 34 ++++- .../icewind/smb/src/Wrapped/Share.php | 14 ++- 20 files changed, 375 insertions(+), 91 deletions(-) create mode 100644 apps/files_external/3rdparty/icewind/smb/src/KerberosApacheAuth.php diff --git a/apps/files_external/3rdparty/.gitignore b/apps/files_external/3rdparty/.gitignore index 651eb60572de8..e787d39fca68c 100644 --- a/apps/files_external/3rdparty/.gitignore +++ b/apps/files_external/3rdparty/.gitignore @@ -5,6 +5,8 @@ icewind/smb/install_libsmbclient.sh icewind/smb/Makefile icewind/smb/.travis.yml icewind/smb/.scrutinizer.yml +icewind/smb/example-apache-kerberos.php +icewind/smb/codecov.yml icewind/streams/tests .github .php_cs* diff --git a/apps/files_external/3rdparty/composer.json b/apps/files_external/3rdparty/composer.json index d8854aa976a5c..21ae38a999642 100644 --- a/apps/files_external/3rdparty/composer.json +++ b/apps/files_external/3rdparty/composer.json @@ -9,6 +9,6 @@ }, "require": { "icewind/streams": "0.7.4", - "icewind/smb": "3.4.1" + "icewind/smb": "3.5.1" } } diff --git a/apps/files_external/3rdparty/composer.lock b/apps/files_external/3rdparty/composer.lock index 05de684a017ab..6235bf98e5c73 100644 --- a/apps/files_external/3rdparty/composer.lock +++ b/apps/files_external/3rdparty/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0ffc772b2aaaaffe52decb8d13361976", + "content-hash": "ed821b15824934fd2d245faca1f35aad", "packages": [ { "name": "icewind/smb", - "version": "v3.4.1", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/icewind1991/SMB.git", - "reference": "9dba42ab2a3990de29e18cc62b0a8270aceb74e3" + "reference": "c1ce4fbb2ff1786846d9d0b3850b395ca94cf563" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/icewind1991/SMB/zipball/9dba42ab2a3990de29e18cc62b0a8270aceb74e3", - "reference": "9dba42ab2a3990de29e18cc62b0a8270aceb74e3", + "url": "https://api.github.com/repos/icewind1991/SMB/zipball/c1ce4fbb2ff1786846d9d0b3850b395ca94cf563", + "reference": "c1ce4fbb2ff1786846d9d0b3850b395ca94cf563", "shasum": "" }, "require": { @@ -49,9 +49,9 @@ "description": "php wrapper for smbclient and libsmbclient-php", "support": { "issues": "https://github.com/icewind1991/SMB/issues", - "source": "https://github.com/icewind1991/SMB/tree/v3.4.1" + "source": "https://github.com/icewind1991/SMB/tree/v3.5.1" }, - "time": "2021-04-19T13:53:08+00:00" + "time": "2021-11-04T14:28:18+00:00" }, { "name": "icewind/streams", diff --git a/apps/files_external/3rdparty/composer/ClassLoader.php b/apps/files_external/3rdparty/composer/ClassLoader.php index 6d0c3f2d001d8..0cd6055d1b794 100644 --- a/apps/files_external/3rdparty/composer/ClassLoader.php +++ b/apps/files_external/3rdparty/composer/ClassLoader.php @@ -42,30 +42,75 @@ */ class ClassLoader { + /** @var ?string */ private $vendorDir; // PSR-4 + /** + * @var array[] + * @psalm-var array> + */ private $prefixLengthsPsr4 = array(); + /** + * @var array[] + * @psalm-var array> + */ private $prefixDirsPsr4 = array(); + /** + * @var array[] + * @psalm-var array + */ private $fallbackDirsPsr4 = array(); // PSR-0 + /** + * @var array[] + * @psalm-var array> + */ private $prefixesPsr0 = array(); + /** + * @var array[] + * @psalm-var array + */ private $fallbackDirsPsr0 = array(); + /** @var bool */ private $useIncludePath = false; + + /** + * @var string[] + * @psalm-var array + */ private $classMap = array(); + + /** @var bool */ private $classMapAuthoritative = false; + + /** + * @var bool[] + * @psalm-var array + */ private $missingClasses = array(); + + /** @var ?string */ private $apcuPrefix; + /** + * @var self[] + */ private static $registeredLoaders = array(); + /** + * @param ?string $vendorDir + */ public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; } + /** + * @return string[] + */ public function getPrefixes() { if (!empty($this->prefixesPsr0)) { @@ -75,28 +120,47 @@ public function getPrefixes() return array(); } + /** + * @return array[] + * @psalm-return array> + */ public function getPrefixesPsr4() { return $this->prefixDirsPsr4; } + /** + * @return array[] + * @psalm-return array + */ public function getFallbackDirs() { return $this->fallbackDirsPsr0; } + /** + * @return array[] + * @psalm-return array + */ public function getFallbackDirsPsr4() { return $this->fallbackDirsPsr4; } + /** + * @return string[] Array of classname => path + * @psalm-var array + */ public function getClassMap() { return $this->classMap; } /** - * @param array $classMap Class to filename map + * @param string[] $classMap Class to filename map + * @psalm-param array $classMap + * + * @return void */ public function addClassMap(array $classMap) { @@ -111,9 +175,11 @@ public function addClassMap(array $classMap) * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * - * @param string $prefix The prefix - * @param array|string $paths The PSR-0 root directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void */ public function add($prefix, $paths, $prepend = false) { @@ -156,11 +222,13 @@ public function add($prefix, $paths, $prepend = false) * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param array|string $paths The PSR-4 base directories - * @param bool $prepend Whether to prepend the directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException + * + * @return void */ public function addPsr4($prefix, $paths, $prepend = false) { @@ -204,8 +272,10 @@ public function addPsr4($prefix, $paths, $prepend = false) * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * - * @param string $prefix The prefix - * @param array|string $paths The PSR-0 base directories + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 base directories + * + * @return void */ public function set($prefix, $paths) { @@ -220,10 +290,12 @@ public function set($prefix, $paths) * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param array|string $paths The PSR-4 base directories + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException + * + * @return void */ public function setPsr4($prefix, $paths) { @@ -243,6 +315,8 @@ public function setPsr4($prefix, $paths) * Turns on searching the include path for class files. * * @param bool $useIncludePath + * + * @return void */ public function setUseIncludePath($useIncludePath) { @@ -265,6 +339,8 @@ public function getUseIncludePath() * that have not been registered with the class map. * * @param bool $classMapAuthoritative + * + * @return void */ public function setClassMapAuthoritative($classMapAuthoritative) { @@ -285,6 +361,8 @@ public function isClassMapAuthoritative() * APCu prefix to use to cache found/not-found classes, if the extension is enabled. * * @param string|null $apcuPrefix + * + * @return void */ public function setApcuPrefix($apcuPrefix) { @@ -305,6 +383,8 @@ public function getApcuPrefix() * Registers this instance as an autoloader. * * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void */ public function register($prepend = false) { @@ -324,6 +404,8 @@ public function register($prepend = false) /** * Unregisters this instance as an autoloader. + * + * @return void */ public function unregister() { @@ -403,6 +485,11 @@ public static function getRegisteredLoaders() return self::$registeredLoaders; } + /** + * @param string $class + * @param string $ext + * @return string|false + */ private function findFileWithExtension($class, $ext) { // PSR-4 lookup @@ -474,6 +561,10 @@ private function findFileWithExtension($class, $ext) * Scope isolated include. * * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + * @private */ function includeFile($file) { diff --git a/apps/files_external/3rdparty/composer/InstalledVersions.php b/apps/files_external/3rdparty/composer/InstalledVersions.php index b3a4e1611e6e5..d50e0c9fcc47d 100644 --- a/apps/files_external/3rdparty/composer/InstalledVersions.php +++ b/apps/files_external/3rdparty/composer/InstalledVersions.php @@ -20,12 +20,25 @@ * * See also https://getcomposer.org/doc/07-runtime.md#installed-versions * - * To require it's presence, you can require `composer-runtime-api ^2.0` + * To require its presence, you can require `composer-runtime-api ^2.0` */ class InstalledVersions { + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}|array{}|null + */ private static $installed; + + /** + * @var bool|null + */ private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ private static $installedByVendor = array(); /** @@ -228,7 +241,7 @@ public static function getInstallPath($packageName) /** * @return array - * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string} + * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string} */ public static function getRootPackage() { @@ -242,7 +255,7 @@ public static function getRootPackage() * * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. * @return array[] - * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array} + * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} */ public static function getRawData() { @@ -265,7 +278,7 @@ public static function getRawData() * Returns the raw data of all installed.php which are currently loaded for custom implementations * * @return array[] - * @psalm-return list}> + * @psalm-return list}> */ public static function getAllRawData() { @@ -288,7 +301,7 @@ public static function getAllRawData() * @param array[] $data A vendor/composer/installed.php data set * @return void * - * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array} $data + * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} $data */ public static function reload($data) { @@ -298,7 +311,7 @@ public static function reload($data) /** * @return array[] - * @psalm-return list}> + * @psalm-return list}> */ private static function getInstalled() { diff --git a/apps/files_external/3rdparty/composer/autoload_classmap.php b/apps/files_external/3rdparty/composer/autoload_classmap.php index d0f82994f2901..17b94af0e9979 100644 --- a/apps/files_external/3rdparty/composer/autoload_classmap.php +++ b/apps/files_external/3rdparty/composer/autoload_classmap.php @@ -48,6 +48,7 @@ 'Icewind\\SMB\\IShare' => $vendorDir . '/icewind/smb/src/IShare.php', 'Icewind\\SMB\\ISystem' => $vendorDir . '/icewind/smb/src/ISystem.php', 'Icewind\\SMB\\ITimeZoneProvider' => $vendorDir . '/icewind/smb/src/ITimeZoneProvider.php', + 'Icewind\\SMB\\KerberosApacheAuth' => $vendorDir . '/icewind/smb/src/KerberosApacheAuth.php', 'Icewind\\SMB\\KerberosAuth' => $vendorDir . '/icewind/smb/src/KerberosAuth.php', 'Icewind\\SMB\\Native\\NativeFileInfo' => $vendorDir . '/icewind/smb/src/Native/NativeFileInfo.php', 'Icewind\\SMB\\Native\\NativeReadStream' => $vendorDir . '/icewind/smb/src/Native/NativeReadStream.php', diff --git a/apps/files_external/3rdparty/composer/autoload_static.php b/apps/files_external/3rdparty/composer/autoload_static.php index 899982f2a678e..1d309dcd6f1ca 100644 --- a/apps/files_external/3rdparty/composer/autoload_static.php +++ b/apps/files_external/3rdparty/composer/autoload_static.php @@ -68,6 +68,7 @@ class ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3 'Icewind\\SMB\\IShare' => __DIR__ . '/..' . '/icewind/smb/src/IShare.php', 'Icewind\\SMB\\ISystem' => __DIR__ . '/..' . '/icewind/smb/src/ISystem.php', 'Icewind\\SMB\\ITimeZoneProvider' => __DIR__ . '/..' . '/icewind/smb/src/ITimeZoneProvider.php', + 'Icewind\\SMB\\KerberosApacheAuth' => __DIR__ . '/..' . '/icewind/smb/src/KerberosApacheAuth.php', 'Icewind\\SMB\\KerberosAuth' => __DIR__ . '/..' . '/icewind/smb/src/KerberosAuth.php', 'Icewind\\SMB\\Native\\NativeFileInfo' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeFileInfo.php', 'Icewind\\SMB\\Native\\NativeReadStream' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeReadStream.php', diff --git a/apps/files_external/3rdparty/composer/installed.json b/apps/files_external/3rdparty/composer/installed.json index 7405962c70cf1..c2e3ffb0e4b77 100644 --- a/apps/files_external/3rdparty/composer/installed.json +++ b/apps/files_external/3rdparty/composer/installed.json @@ -2,17 +2,17 @@ "packages": [ { "name": "icewind/smb", - "version": "v3.4.1", - "version_normalized": "3.4.1.0", + "version": "v3.5.1", + "version_normalized": "3.5.1.0", "source": { "type": "git", "url": "https://github.com/icewind1991/SMB.git", - "reference": "9dba42ab2a3990de29e18cc62b0a8270aceb74e3" + "reference": "c1ce4fbb2ff1786846d9d0b3850b395ca94cf563" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/icewind1991/SMB/zipball/9dba42ab2a3990de29e18cc62b0a8270aceb74e3", - "reference": "9dba42ab2a3990de29e18cc62b0a8270aceb74e3", + "url": "https://api.github.com/repos/icewind1991/SMB/zipball/c1ce4fbb2ff1786846d9d0b3850b395ca94cf563", + "reference": "c1ce4fbb2ff1786846d9d0b3850b395ca94cf563", "shasum": "" }, "require": { @@ -25,7 +25,7 @@ "phpunit/phpunit": "^8.5|^9.3.8", "psalm/phar": "^4.3" }, - "time": "2021-04-19T13:53:08+00:00", + "time": "2021-11-04T14:28:18+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -46,7 +46,7 @@ "description": "php wrapper for smbclient and libsmbclient-php", "support": { "issues": "https://github.com/icewind1991/SMB/issues", - "source": "https://github.com/icewind1991/SMB/tree/v3.4.1" + "source": "https://github.com/icewind1991/SMB/tree/v3.5.1" }, "install-path": "../icewind/smb" }, diff --git a/apps/files_external/3rdparty/composer/installed.php b/apps/files_external/3rdparty/composer/installed.php index 7dfd1c420f7e8..2b4e3329b365a 100644 --- a/apps/files_external/3rdparty/composer/installed.php +++ b/apps/files_external/3rdparty/composer/installed.php @@ -5,7 +5,7 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), - 'reference' => '70483a16a3a232758979bb6fa363629b5a16b6a4', + 'reference' => 'cd72330b8f669e3dc81388be5a92171404f36fec', 'name' => 'files_external/3rdparty', 'dev' => true, ), @@ -16,16 +16,16 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), - 'reference' => '70483a16a3a232758979bb6fa363629b5a16b6a4', + 'reference' => 'cd72330b8f669e3dc81388be5a92171404f36fec', 'dev_requirement' => false, ), 'icewind/smb' => array( - 'pretty_version' => 'v3.4.1', - 'version' => '3.4.1.0', + 'pretty_version' => 'v3.5.1', + 'version' => '3.5.1.0', 'type' => 'library', 'install_path' => __DIR__ . '/../icewind/smb', 'aliases' => array(), - 'reference' => '9dba42ab2a3990de29e18cc62b0a8270aceb74e3', + 'reference' => 'c1ce4fbb2ff1786846d9d0b3850b395ca94cf563', 'dev_requirement' => false, ), 'icewind/streams' => array( diff --git a/apps/files_external/3rdparty/icewind/smb/README.md b/apps/files_external/3rdparty/icewind/smb/README.md index 272c4ebedcdb8..fec1faefbadb6 100644 --- a/apps/files_external/3rdparty/icewind/smb/README.md +++ b/apps/files_external/3rdparty/icewind/smb/README.md @@ -44,13 +44,42 @@ $server = $serverFactory->createServer('localhost', $auth); ### Using kerberos authentication ### +There are two ways of using kerberos to authenticate against the smb server: + +- Using a ticket from the php server +- Re-using a ticket send by the client + +### Using a server ticket + +Using a server ticket allows the web server to authenticate against the smb server using an existing machine account. + +The ticket needs to be available in the environment of the php process. + ```php $serverFactory = new ServerFactory(); $auth = new KerberosAuth(); $server = $serverFactory->createServer('localhost', $auth); ``` -Note that this requires a valid kerberos ticket to already be available for php +### Re-using a client ticket + +By re-using a client ticket you can create a single sign-on setup where the user authenticates against +the web service using kerberos. And the web server can forward that ticket to the smb server, allowing it +to act on the behalf of the user without requiring the user to enter his passord. + +The setup for such a system is fairly involved and requires roughly the following this + +- The web server is authenticated against kerberos with a machine account +- Delegation is enabled for the web server's machine account +- Apache is setup to perform kerberos authentication and save the ticket in it's environment +- Php has the krb5 extension installed +- The client authenticates using a ticket with forwarding enabled + +```php +$serverFactory = new ServerFactory(); +$auth = new KerberosApacheAuth(); +$server = $serverFactory->createServer('localhost', $auth); +``` ### Upload a file ### diff --git a/apps/files_external/3rdparty/icewind/smb/src/IShare.php b/apps/files_external/3rdparty/icewind/smb/src/IShare.php index 6ac6e0d2d159e..40213b93a9963 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/IShare.php +++ b/apps/files_external/3rdparty/icewind/smb/src/IShare.php @@ -45,7 +45,7 @@ public function get(string $source, string $target): bool; public function put(string $source, string $target): bool; /** - * Open a readable stream top a remote file + * Open a readable stream to a remote file * * @param string $source * @return resource a read only stream with the contents of the remote file diff --git a/apps/files_external/3rdparty/icewind/smb/src/KerberosApacheAuth.php b/apps/files_external/3rdparty/icewind/smb/src/KerberosApacheAuth.php new file mode 100644 index 0000000000000..03551aa6f3496 --- /dev/null +++ b/apps/files_external/3rdparty/icewind/smb/src/KerberosApacheAuth.php @@ -0,0 +1,117 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ + +namespace Icewind\SMB; + +use Icewind\SMB\Exception\DependencyException; +use Icewind\SMB\Exception\Exception; + +/** + * Use existing kerberos ticket to authenticate and reuse the apache ticket cache (mod_auth_kerb) + */ +class KerberosApacheAuth extends KerberosAuth implements IAuth { + /** @var string */ + private $ticketPath = ""; + + // only working with specific library (mod_auth_kerb, krb5, smbclient) versions + /** @var bool */ + private $saveTicketInMemory = false; + + /** @var bool */ + private $init = false; + + /** + * @param bool $saveTicketInMemory + */ + public function __construct(bool $saveTicketInMemory = false) { + $this->saveTicketInMemory = $saveTicketInMemory; + } + + /** + * Check if a valid kerberos ticket is present + * + * @return bool + */ + public function checkTicket(): bool { + //read apache kerberos ticket cache + $cacheFile = getenv("KRB5CCNAME"); + if (!$cacheFile) { + return false; + } + + $krb5 = new \KRB5CCache(); + $krb5->open($cacheFile); + return (bool)$krb5->isValid(); + } + + private function init(): void { + if ($this->init) { + return; + } + $this->init = true; + // inspired by https://git.typo3.org/TYPO3CMS/Extensions/fal_cifs.git + + if (!extension_loaded("krb5")) { + // https://pecl.php.net/package/krb5 + throw new DependencyException('Ensure php-krb5 is installed.'); + } + + //read apache kerberos ticket cache + $cacheFile = getenv("KRB5CCNAME"); + if (!$cacheFile) { + throw new Exception('No kerberos ticket cache environment variable (KRB5CCNAME) found.'); + } + + $krb5 = new \KRB5CCache(); + $krb5->open($cacheFile); + if (!$krb5->isValid()) { + throw new Exception('Kerberos ticket cache is not valid.'); + } + + + if ($this->saveTicketInMemory) { + putenv("KRB5CCNAME=" . (string)$krb5->getName()); + } else { + //workaround: smbclient is not working with the original apache ticket cache. + $tmpFilename = tempnam("/tmp", "krb5cc_php_"); + $tmpCacheFile = "FILE:" . $tmpFilename; + $krb5->save($tmpCacheFile); + $this->ticketPath = $tmpFilename; + putenv("KRB5CCNAME=" . $tmpCacheFile); + } + } + + public function getExtraCommandLineArguments(): string { + $this->init(); + return parent::getExtraCommandLineArguments(); + } + + public function setExtraSmbClientOptions($smbClientState): void { + $this->init(); + parent::setExtraSmbClientOptions($smbClientState); + } + + public function __destruct() { + if (!empty($this->ticketPath) && file_exists($this->ticketPath) && is_file($this->ticketPath)) { + unlink($this->ticketPath); + } + } +} diff --git a/apps/files_external/3rdparty/icewind/smb/src/Native/NativeFileInfo.php b/apps/files_external/3rdparty/icewind/smb/src/Native/NativeFileInfo.php index 539bb72842671..85fb0274ac18e 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Native/NativeFileInfo.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Native/NativeFileInfo.php @@ -99,7 +99,7 @@ protected function getMode(): int { public function isDirectory(): bool { $mode = $this->getMode(); if ($mode > 0x1000) { - return (bool)($mode & 0x4000); // 0x4000: unix directory flag + return ($mode & 0x4000 && !($mode & 0x8000)); // 0x4000: unix directory flag shares bits with 0xC000: socket } else { return (bool)($mode & IFileInfo::MODE_DIRECTORY); } diff --git a/apps/files_external/3rdparty/icewind/smb/src/Native/NativeShare.php b/apps/files_external/3rdparty/icewind/smb/src/Native/NativeShare.php index 03ec501b830e3..8c4eab2a60f90 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Native/NativeShare.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Native/NativeShare.php @@ -267,14 +267,14 @@ public function read(string $source) { * Open a writeable stream to a remote file * Note: This method will truncate the file to 0bytes first * - * @param string $source + * @param string $target * @return resource a writeable stream * * @throws NotFoundException * @throws InvalidTypeException */ - public function write(string $source) { - $url = $this->buildUrl($source); + public function write(string $target) { + $url = $this->buildUrl($target); $handle = $this->getState()->create($url); return NativeWriteStream::wrap($this->getState(), $handle, 'w', $url); } @@ -282,14 +282,14 @@ public function write(string $source) { /** * Open a writeable stream and set the cursor to the end of the stream * - * @param string $source + * @param string $target * @return resource a writeable stream * * @throws NotFoundException * @throws InvalidTypeException */ - public function append(string $source) { - $url = $this->buildUrl($source); + public function append(string $target) { + $url = $this->buildUrl($target); $handle = $this->getState()->open($url, "a+"); return NativeWriteStream::wrap($this->getState(), $handle, "a", $url); } diff --git a/apps/files_external/3rdparty/icewind/smb/src/Native/NativeState.php b/apps/files_external/3rdparty/icewind/smb/src/Native/NativeState.php index e1a13ce3e72f7..088e6f733d123 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Native/NativeState.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Native/NativeState.php @@ -38,6 +38,14 @@ class NativeState { /** @var bool */ protected $connected = false; + /** + * sync the garbage collection cycle + * __deconstruct() of KerberosAuth should not called too soon + * + * @var IAuth|null $auth + */ + protected $auth = null; + // see error.h const EXCEPTION_MAP = [ 1 => ForbiddenException::class, @@ -107,6 +115,11 @@ public function init(IAuth $auth, IOptions $options) { } $auth->setExtraSmbClientOptions($this->state); + + // sync the garbage collection cycle + // __deconstruct() of KerberosAuth should not caled too soon + $this->auth = $auth; + /** @var bool $result */ $result = @smbclient_state_init($this->state, $auth->getWorkgroup(), $auth->getUsername(), $auth->getPassword()); diff --git a/apps/files_external/3rdparty/icewind/smb/src/System.php b/apps/files_external/3rdparty/icewind/smb/src/System.php index 919907477abc6..d3475e7a5cb6e 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/System.php +++ b/apps/files_external/3rdparty/icewind/smb/src/System.php @@ -62,7 +62,7 @@ protected function getBinaryPath(string $binary): ?string { $result = null; $output = []; exec("which $binary 2>&1", $output, $result); - $this->paths[$binary] = $result === 0 ? trim(implode('', $output)) : null; + $this->paths[$binary] = $result === 0 && isset($output[0]) ? (string)$output[0] : null; } return $this->paths[$binary]; } diff --git a/apps/files_external/3rdparty/icewind/smb/src/Wrapped/Connection.php b/apps/files_external/3rdparty/icewind/smb/src/Wrapped/Connection.php index 31b72b05d97b2..cc73ac1ad14c6 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Wrapped/Connection.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Wrapped/Connection.php @@ -47,7 +47,7 @@ public function write(string $input) { public function clearTillPrompt(): void { $this->write(''); do { - $promptLine = $this->readLine(); + $promptLine = $this->readTillPrompt(); if ($promptLine === false) { break; } @@ -56,13 +56,12 @@ public function clearTillPrompt(): void { if ($this->write('') === false) { throw new ConnectionRefusedException(); } - $this->readLine(); + $this->readTillPrompt(); } /** * get all unprocessed output from smbclient until the next prompt * - * @param (callable(string):bool)|null $callback (optional) callback to call for every line read * @return string[] * @throws AuthenticationException * @throws ConnectException @@ -71,42 +70,22 @@ public function clearTillPrompt(): void { * @throws NoLoginServerException * @throws AccessDeniedException */ - public function read(callable $callback = null): array { + public function read(): array { if (!$this->isValid()) { throw new ConnectionException('Connection not valid'); } - $promptLine = $this->readLine(); //first line is prompt - if ($promptLine === false) { - $this->unknownError($promptLine); - } - $this->parser->checkConnectionError($promptLine); - - $output = []; - if (!$this->isPrompt($promptLine)) { - $line = $promptLine; - } else { - $line = $this->readLine(); - } - if ($line === false) { - $this->unknownError($promptLine); - } - while ($line !== false && !$this->isPrompt($line)) { //next prompt functions as delimiter - if (is_callable($callback)) { - $result = $callback($line); - if ($result === false) { // allow the callback to close the connection for infinite running commands - $this->close(true); - break; - } - } else { - $output[] = $line; - } - $line = $this->readLine(); + $output = $this->readTillPrompt(); + if ($output === false) { + $this->unknownError(false); } + $output = explode("\n", $output); + // last line contains the prompt + array_pop($output); return $output; } private function isPrompt(string $line): bool { - return mb_substr($line, 0, self::DELIMITER_LENGTH) === self::DELIMITER; + return substr($line, 0, self::DELIMITER_LENGTH) === self::DELIMITER; } /** @@ -132,6 +111,6 @@ public function close(bool $terminate = true): void { // ignore any errors while trying to send the close command, the process might already be dead @$this->write('close' . PHP_EOL); } - parent::close($terminate); + $this->close_process($terminate); } } diff --git a/apps/files_external/3rdparty/icewind/smb/src/Wrapped/NotifyHandler.php b/apps/files_external/3rdparty/icewind/smb/src/Wrapped/NotifyHandler.php index 18451f4daa69c..ecb5bb1e3c112 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Wrapped/NotifyHandler.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Wrapped/NotifyHandler.php @@ -65,16 +65,20 @@ public function getChanges(): array { */ public function listen(callable $callback): void { if ($this->listening) { - $this->connection->read(function (string $line) use ($callback): bool { + while (true) { + $line = $this->connection->readLine(); + if ($line === false) { + break; + } $this->checkForError($line); $change = $this->parseChangeLine($line); if ($change) { $result = $callback($change); - return $result === false ? false : true; - } else { - return true; + if ($result === false) { + break; + } } - }); + }; } } diff --git a/apps/files_external/3rdparty/icewind/smb/src/Wrapped/RawConnection.php b/apps/files_external/3rdparty/icewind/smb/src/Wrapped/RawConnection.php index 26a17cc584b8a..4aec674c3da0f 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Wrapped/RawConnection.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Wrapped/RawConnection.php @@ -71,7 +71,8 @@ public function connect(): void { setlocale(LC_ALL, Server::LOCALE); $env = array_merge($this->env, [ - 'CLI_FORCE_INTERACTIVE' => 'y', // Needed or the prompt isn't displayed!! + 'CLI_FORCE_INTERACTIVE' => 'y', // Make sure the prompt is displayed + 'CLI_NO_READLINE' => 1, // Not all distros build smbclient with readline, disable it to get consistent behaviour 'LC_ALL' => Server::LOCALE, 'LANG' => Server::LOCALE, 'COLUMNS' => 8192 // prevent smbclient from line-wrapping it's output @@ -91,7 +92,7 @@ public function connect(): void { public function isValid(): bool { if (is_resource($this->process)) { $status = proc_get_status($this->process); - return (bool)$status['running']; + return $status['running']; } else { return false; } @@ -109,13 +110,30 @@ public function write(string $input) { return $result; } + /** + * read output till the next prompt + * + * @return string|false + */ + public function readTillPrompt() { + $output = ""; + do { + $chunk = $this->readLine('\> '); + if ($chunk === false) { + return false; + } + $output .= $chunk; + } while (strlen($chunk) == 4096 && strpos($chunk, "smb:") === false); + return $output; + } + /** * read a line of output * * @return string|false */ - public function readLine() { - return stream_get_line($this->getOutputStream(), 4086, "\n"); + public function readLine(string $end = "\n") { + return stream_get_line($this->getOutputStream(), 4096, $end); } /** @@ -202,6 +220,14 @@ public function writeAuthentication(?string $user, ?string $password): void { * @psalm-assert null $this->process */ public function close(bool $terminate = true): void { + $this->close_process($terminate); + } + + /** + * @param bool $terminate + * @psalm-assert null $this->process + */ + protected function close_process(bool $terminate = true): void { if (!is_resource($this->process)) { return; } diff --git a/apps/files_external/3rdparty/icewind/smb/src/Wrapped/Share.php b/apps/files_external/3rdparty/icewind/smb/src/Wrapped/Share.php index 68446d380e08d..eb68d3800b382 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Wrapped/Share.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Wrapped/Share.php @@ -345,11 +345,17 @@ public function read(string $source) { // since returned stream is closed by the caller we need to create a new instance // since we can't re-use the same file descriptor over multiple calls $connection = $this->getConnection(); + stream_set_blocking($connection->getOutputStream(), false); $connection->write('get ' . $source . ' ' . $this->system->getFD(5)); $connection->write('exit'); $fh = $connection->getFileOutputStream(); - stream_context_set_option($fh, 'file', 'connection', $connection); + $fh = CallbackWrapper::wrap($fh, function() use ($connection) { + $connection->write(''); + }); + if (!is_resource($fh)) { + throw new Exception("Failed to wrap file output"); + } return $fh; } @@ -374,7 +380,9 @@ public function write(string $target) { // use a close callback to ensure the upload is finished before continuing // this also serves as a way to keep the connection in scope - $stream = CallbackWrapper::wrap($fh, null, null, function () use ($connection) { + $stream = CallbackWrapper::wrap($fh, function() use ($connection) { + $connection->write(''); + }, null, function () use ($connection) { $connection->close(false); // dont terminate, give the upload some time }); if (is_resource($stream)) { @@ -446,7 +454,7 @@ public function notify(string $path): INotifyHandler { * @return string[] */ protected function execute(string $command): array { - $this->connect()->write($command . PHP_EOL); + $this->connect()->write($command); return $this->connect()->read(); } From 48f6d0f2f6cbf35ab5a5c9fc0dc682d40002bfb1 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 4 Nov 2021 15:38:42 +0100 Subject: [PATCH 5/7] update to release smb lib Signed-off-by: Robin Appelman --- apps/files_external/lib/Lib/Backend/SMB.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/files_external/lib/Lib/Backend/SMB.php b/apps/files_external/lib/Lib/Backend/SMB.php index b6854e6938d19..57ee866f3c793 100644 --- a/apps/files_external/lib/Lib/Backend/SMB.php +++ b/apps/files_external/lib/Lib/Backend/SMB.php @@ -94,10 +94,10 @@ public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = n throw new \InvalidArgumentException('invalid authentication backend'); } $credentialsStore = $auth->getCredentialsStore(); - $kerb_auth = new KerberosApacheAuth(); - if ($kerb_auth->checkTicket()) { - $kerb_auth->registerApacheKerberosTicket(); - $smbAuth = $kerb_auth; + $kerbAuth = new KerberosApacheAuth(); + // check if a kerberos ticket is available, else fallback to session credentials + if ($kerbAuth->checkTicket()) { + $smbAuth = $kerbAuth; } else { try { $credentials = $credentialsStore->getLoginCredentials(); From 0cdcccf9feaf47e39d9b8711ca17d3c43046d66a Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 20 Jan 2022 16:09:41 +0100 Subject: [PATCH 6/7] update icewind/smb to 3.5.2 Signed-off-by: Robin Appelman --- apps/files_external/3rdparty/composer.json | 2 +- apps/files_external/3rdparty/composer.lock | 16 ++-- .../3rdparty/composer/ClassLoader.php | 2 +- .../3rdparty/composer/installed.json | 14 ++-- .../3rdparty/composer/installed.php | 10 +-- .../icewind/smb/src/KerberosApacheAuth.php | 81 ++++++++++++------- 6 files changed, 72 insertions(+), 53 deletions(-) diff --git a/apps/files_external/3rdparty/composer.json b/apps/files_external/3rdparty/composer.json index 21ae38a999642..e343521add37f 100644 --- a/apps/files_external/3rdparty/composer.json +++ b/apps/files_external/3rdparty/composer.json @@ -9,6 +9,6 @@ }, "require": { "icewind/streams": "0.7.4", - "icewind/smb": "3.5.1" + "icewind/smb": "3.5.2" } } diff --git a/apps/files_external/3rdparty/composer.lock b/apps/files_external/3rdparty/composer.lock index 6235bf98e5c73..918dd7da08d47 100644 --- a/apps/files_external/3rdparty/composer.lock +++ b/apps/files_external/3rdparty/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ed821b15824934fd2d245faca1f35aad", + "content-hash": "524c99fd87297e01d004eb5a4e53b04c", "packages": [ { "name": "icewind/smb", - "version": "v3.5.1", + "version": "v3.5.2", "source": { "type": "git", "url": "https://github.com/icewind1991/SMB.git", - "reference": "c1ce4fbb2ff1786846d9d0b3850b395ca94cf563" + "reference": "0a425bd21acf7ae112b135dca34640e1b1a825c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/icewind1991/SMB/zipball/c1ce4fbb2ff1786846d9d0b3850b395ca94cf563", - "reference": "c1ce4fbb2ff1786846d9d0b3850b395ca94cf563", + "url": "https://api.github.com/repos/icewind1991/SMB/zipball/0a425bd21acf7ae112b135dca34640e1b1a825c3", + "reference": "0a425bd21acf7ae112b135dca34640e1b1a825c3", "shasum": "" }, "require": { @@ -49,9 +49,9 @@ "description": "php wrapper for smbclient and libsmbclient-php", "support": { "issues": "https://github.com/icewind1991/SMB/issues", - "source": "https://github.com/icewind1991/SMB/tree/v3.5.1" + "source": "https://github.com/icewind1991/SMB/tree/v3.5.2" }, - "time": "2021-11-04T14:28:18+00:00" + "time": "2022-01-20T14:51:51+00:00" }, { "name": "icewind/streams", @@ -107,5 +107,5 @@ "prefer-lowest": false, "platform": [], "platform-dev": [], - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.2.0" } diff --git a/apps/files_external/3rdparty/composer/ClassLoader.php b/apps/files_external/3rdparty/composer/ClassLoader.php index 0cd6055d1b794..afef3fa2ad83f 100644 --- a/apps/files_external/3rdparty/composer/ClassLoader.php +++ b/apps/files_external/3rdparty/composer/ClassLoader.php @@ -149,7 +149,7 @@ public function getFallbackDirsPsr4() /** * @return string[] Array of classname => path - * @psalm-var array + * @psalm-return array */ public function getClassMap() { diff --git a/apps/files_external/3rdparty/composer/installed.json b/apps/files_external/3rdparty/composer/installed.json index c2e3ffb0e4b77..2c9b1c087dfc0 100644 --- a/apps/files_external/3rdparty/composer/installed.json +++ b/apps/files_external/3rdparty/composer/installed.json @@ -2,17 +2,17 @@ "packages": [ { "name": "icewind/smb", - "version": "v3.5.1", - "version_normalized": "3.5.1.0", + "version": "v3.5.2", + "version_normalized": "3.5.2.0", "source": { "type": "git", "url": "https://github.com/icewind1991/SMB.git", - "reference": "c1ce4fbb2ff1786846d9d0b3850b395ca94cf563" + "reference": "0a425bd21acf7ae112b135dca34640e1b1a825c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/icewind1991/SMB/zipball/c1ce4fbb2ff1786846d9d0b3850b395ca94cf563", - "reference": "c1ce4fbb2ff1786846d9d0b3850b395ca94cf563", + "url": "https://api.github.com/repos/icewind1991/SMB/zipball/0a425bd21acf7ae112b135dca34640e1b1a825c3", + "reference": "0a425bd21acf7ae112b135dca34640e1b1a825c3", "shasum": "" }, "require": { @@ -25,7 +25,7 @@ "phpunit/phpunit": "^8.5|^9.3.8", "psalm/phar": "^4.3" }, - "time": "2021-11-04T14:28:18+00:00", + "time": "2022-01-20T14:51:51+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -46,7 +46,7 @@ "description": "php wrapper for smbclient and libsmbclient-php", "support": { "issues": "https://github.com/icewind1991/SMB/issues", - "source": "https://github.com/icewind1991/SMB/tree/v3.5.1" + "source": "https://github.com/icewind1991/SMB/tree/v3.5.2" }, "install-path": "../icewind/smb" }, diff --git a/apps/files_external/3rdparty/composer/installed.php b/apps/files_external/3rdparty/composer/installed.php index 2b4e3329b365a..255420e100321 100644 --- a/apps/files_external/3rdparty/composer/installed.php +++ b/apps/files_external/3rdparty/composer/installed.php @@ -5,7 +5,7 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), - 'reference' => 'cd72330b8f669e3dc81388be5a92171404f36fec', + 'reference' => '0bed61f949bc7a8c69cd154919e78b704e28c99e', 'name' => 'files_external/3rdparty', 'dev' => true, ), @@ -16,16 +16,16 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), - 'reference' => 'cd72330b8f669e3dc81388be5a92171404f36fec', + 'reference' => '0bed61f949bc7a8c69cd154919e78b704e28c99e', 'dev_requirement' => false, ), 'icewind/smb' => array( - 'pretty_version' => 'v3.5.1', - 'version' => '3.5.1.0', + 'pretty_version' => 'v3.5.2', + 'version' => '3.5.2.0', 'type' => 'library', 'install_path' => __DIR__ . '/../icewind/smb', 'aliases' => array(), - 'reference' => 'c1ce4fbb2ff1786846d9d0b3850b395ca94cf563', + 'reference' => '0a425bd21acf7ae112b135dca34640e1b1a825c3', 'dev_requirement' => false, ), 'icewind/streams' => array( diff --git a/apps/files_external/3rdparty/icewind/smb/src/KerberosApacheAuth.php b/apps/files_external/3rdparty/icewind/smb/src/KerberosApacheAuth.php index 03551aa6f3496..c49918be114a6 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/KerberosApacheAuth.php +++ b/apps/files_external/3rdparty/icewind/smb/src/KerberosApacheAuth.php @@ -31,35 +31,65 @@ class KerberosApacheAuth extends KerberosAuth implements IAuth { /** @var string */ private $ticketPath = ""; - // only working with specific library (mod_auth_kerb, krb5, smbclient) versions - /** @var bool */ - private $saveTicketInMemory = false; - /** @var bool */ private $init = false; + /** @var string|false */ + private $ticketName; + + public function __construct() { + $this->ticketName = getenv("KRB5CCNAME"); + } + + + /** + * Copy the ticket to a temporary location and use that ticket for authentication + * + * @return void + */ + public function copyTicket(): void { + if (!$this->checkTicket()) { + return; + } + $krb5 = new \KRB5CCache(); + $krb5->open($this->ticketName); + $tmpFilename = tempnam("/tmp", "krb5cc_php_"); + $tmpCacheFile = "FILE:" . $tmpFilename; + $krb5->save($tmpCacheFile); + $this->ticketPath = $tmpFilename; + $this->ticketName = $tmpCacheFile; + } + /** - * @param bool $saveTicketInMemory + * Pass the ticket to smbclient by memory instead of path + * + * @return void */ - public function __construct(bool $saveTicketInMemory = false) { - $this->saveTicketInMemory = $saveTicketInMemory; + public function passTicketFromMemory(): void { + if (!$this->checkTicket()) { + return; + } + $krb5 = new \KRB5CCache(); + $krb5->open($this->ticketName); + $this->ticketName = (string)$krb5->getName(); } /** * Check if a valid kerberos ticket is present * * @return bool + * @psalm-assert-if-true string $this->ticketName */ public function checkTicket(): bool { //read apache kerberos ticket cache - $cacheFile = getenv("KRB5CCNAME"); - if (!$cacheFile) { + if (!$this->ticketName) { return false; } $krb5 = new \KRB5CCache(); - $krb5->open($cacheFile); - return (bool)$krb5->isValid(); + $krb5->open($this->ticketName); + /** @psalm-suppress MixedArgument */ + return count($krb5->getEntries()) > 0; } private function init(): void { @@ -75,28 +105,13 @@ private function init(): void { } //read apache kerberos ticket cache - $cacheFile = getenv("KRB5CCNAME"); - if (!$cacheFile) { + if (!$this->checkTicket()) { throw new Exception('No kerberos ticket cache environment variable (KRB5CCNAME) found.'); } - $krb5 = new \KRB5CCache(); - $krb5->open($cacheFile); - if (!$krb5->isValid()) { - throw new Exception('Kerberos ticket cache is not valid.'); - } - - - if ($this->saveTicketInMemory) { - putenv("KRB5CCNAME=" . (string)$krb5->getName()); - } else { - //workaround: smbclient is not working with the original apache ticket cache. - $tmpFilename = tempnam("/tmp", "krb5cc_php_"); - $tmpCacheFile = "FILE:" . $tmpFilename; - $krb5->save($tmpCacheFile); - $this->ticketPath = $tmpFilename; - putenv("KRB5CCNAME=" . $tmpCacheFile); - } + // note that even if the ticketname is the value we got from `getenv("KRB5CCNAME")` we still need to set the env variable ourselves + // this is because `getenv` also reads the variables passed from the SAPI (apache-php) and we need to set the variable in the OS's env + putenv("KRB5CCNAME=" . $this->ticketName); } public function getExtraCommandLineArguments(): string { @@ -106,7 +121,11 @@ public function getExtraCommandLineArguments(): string { public function setExtraSmbClientOptions($smbClientState): void { $this->init(); - parent::setExtraSmbClientOptions($smbClientState); + try { + parent::setExtraSmbClientOptions($smbClientState); + } catch (Exception $e) { + // suppress + } } public function __destruct() { From 13b8179912630acede43aff844f2d302f552cb80 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 20 Jan 2022 17:16:08 +0100 Subject: [PATCH 7/7] Add test for samba kerberos sso in ci Signed-off-by: Robin Appelman --- .github/workflows/smb-kerberos.yml | 78 ++++++++++++++++++++++++++ apps/files_external/tests/setup-krb.sh | 33 +++++++++++ 2 files changed, 111 insertions(+) create mode 100644 .github/workflows/smb-kerberos.yml create mode 100755 apps/files_external/tests/setup-krb.sh diff --git a/.github/workflows/smb-kerberos.yml b/.github/workflows/smb-kerberos.yml new file mode 100644 index 0000000000000..2875a7f4fbd20 --- /dev/null +++ b/.github/workflows/smb-kerberos.yml @@ -0,0 +1,78 @@ +name: Samba Kerberos SSO +on: + push: + branches: + - master + - stable* + paths: + - 'apps/files_external/**' + pull_request: + paths: + - 'apps/files_external/**' + +jobs: + smb-kerberos-tests: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php-versions: ['7.4', '8.0'] + + name: php${{ matrix.php-versions }}-${{ matrix.ftpd }} + + steps: + - name: Checkout server + uses: actions/checkout@v2 + with: + submodules: true + - name: Pull images + run: | + docker pull icewind1991/samba-krb-test-dc + docker pull icewind1991/samba-krb-test-apache + docker pull icewind1991/samba-krb-test-client + - name: Setup AD-DC + run: | + mkdir data + sudo chown -R 33 data apps config + apps/files_external/tests/setup-krb.sh + - name: Set up Nextcloud + run: | + docker exec --user 33 apache ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password + docker exec --user 33 apache ./occ config:system:set trusted_domains 1 --value 'httpd.domain.test' + + # setup user_saml + docker exec --user 33 apache ./occ app:enable user_saml --force + docker exec --user 33 apache ./occ config:app:set user_saml type --value 'environment-variable' + docker exec --user 33 apache ./occ config:app:set user_saml general-uid_mapping --value REMOTE_USER + + # setup external storage + docker exec --user 33 apache ./occ app:enable files_external --force + docker exec --user 33 apache ./occ files_external:create smb smb smb::kerberosapache + docker exec --user 33 apache ./occ files_external:config 1 host krb.domain.test + docker exec --user 33 apache ./occ files_external:config 1 share netlogon + docker exec --user 33 apache ./occ files_external:list + - name: Test SSO + run: | + mkdir cookies + chmod 0777 cookies + + DC_IP=$(docker inspect dc --format '{{.NetworkSettings.IPAddress}}') + docker run --rm --name client -v $PWD/cookies:/cookies -v /tmp/shared:/shared --dns $DC_IP --hostname client.domain.test icewind1991/samba-krb-test-client \ + curl -c /cookies/jar -s --negotiate -u testuser@DOMAIN.TEST: --delegation always http://httpd.domain.test/index.php/apps/user_saml/saml/login + CONTENT=$(docker run --rm --name client -v $PWD/cookies:/cookies -v /tmp/shared:/shared --dns $DC_IP --hostname client.domain.test icewind1991/samba-krb-test-client \ + curl -b /cookies/jar -s --negotiate -u testuser@DOMAIN.TEST: --delegation always http://httpd.domain.test/remote.php/webdav/smb/test.txt) + echo $CONTENT + CONTENT=$(echo $CONTENT | tr -d '[:space:]') + [[ $CONTENT == "testfile" ]] + + + smb-kerberos-summary: + runs-on: ubuntu-latest + needs: smb-kerberos-tests + + if: always() + + steps: + - name: Summary status + run: if ${{ needs.smb-kerberos-tests.result != 'success' }}; then exit 1; fi diff --git a/apps/files_external/tests/setup-krb.sh b/apps/files_external/tests/setup-krb.sh new file mode 100755 index 0000000000000..968ba80529e22 --- /dev/null +++ b/apps/files_external/tests/setup-krb.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +function getContainerHealth { + docker inspect --format "{{.State.Health.Status}}" $1 +} + +function waitContainer { + while STATUS=$(getContainerHealth $1); [ $STATUS != "healthy" ]; do + if [ $STATUS == "unhealthy" ]; then + echo "Failed!" + exit -1 + fi + printf . + lf=$'\n' + sleep 1 + done + printf "$lf" +} + +mkdir /tmp/shared + +# start the dc +docker run -dit --name dc -v /tmp/shared:/shared --hostname krb.domain.test --cap-add SYS_ADMIN icewind1991/samba-krb-test-dc +DC_IP=$(docker inspect dc --format '{{.NetworkSettings.IPAddress}}') + +waitContainer dc + +# start apache +docker run -d --name apache -v $PWD:/var/www/html -v /tmp/shared:/shared --dns $DC_IP --hostname httpd.domain.test icewind1991/samba-krb-test-apache +APACHE_IP=$(docker inspect apache --format '{{.NetworkSettings.IPAddress}}') + +# add the dns record for apache +docker exec dc samba-tool dns add krb.domain.test domain.test httpd A $APACHE_IP -U administrator --password=passwOrd1